Merge branch 'master' of github.com:radian-software/riju into radian-software-master
This commit is contained in:
commit
1dd7533abe
|
@ -1,8 +1,12 @@
|
||||||
github: raxod502
|
github: radian-software
|
||||||
patreon: riju
|
patreon: radiansoftware
|
||||||
ko_fi: riju_codes
|
ko_fi: radiansoftware
|
||||||
liberapay: riju
|
liberapay: radian-software
|
||||||
custom:
|
custom:
|
||||||
- https://paypal.me/rijucodes
|
- https://www.paypal.com/donate/?hosted_button_id=SYF48KFJ95FPA
|
||||||
- https://cash.app/$RijuCodes
|
- https://cash.app/$RadianSoftware
|
||||||
- https://venmo.com/code?user_id=3335527067549696598
|
# Venmo is not currently supported because it is impossible to
|
||||||
|
# create a new business account when one has been created at any
|
||||||
|
# point in the past, even if it has been deleted. I have reached out
|
||||||
|
# to Venmo support and they have confirmed there is no supported way
|
||||||
|
# to use Venmo going forward, and suggested I use PayPal instead.
|
||||||
|
|
|
@ -1,17 +0,0 @@
|
||||||
name: Build and deploy
|
|
||||||
on:
|
|
||||||
push:
|
|
||||||
branches:
|
|
||||||
- master
|
|
||||||
concurrency: deploy
|
|
||||||
jobs:
|
|
||||||
deploy:
|
|
||||||
runs-on: ubuntu-latest
|
|
||||||
steps:
|
|
||||||
- uses: actions/checkout@v2
|
|
||||||
# - name: Build and deploy
|
|
||||||
# env:
|
|
||||||
# AWS_ACCESS_KEY_ID: ${{ secrets.AWS_ACCESS_KEY_ID }}
|
|
||||||
# AWS_REGION: us-west-1
|
|
||||||
# AWS_SECRET_ACCESS_KEY: ${{ secrets.AWS_SECRET_ACCESS_KEY }}
|
|
||||||
# run: tools/ci-ec2.bash
|
|
|
@ -1,11 +1,14 @@
|
||||||
*.log
|
*.log
|
||||||
|
*.out
|
||||||
|
*.out.*
|
||||||
*.pem
|
*.pem
|
||||||
.env
|
.env
|
||||||
.lsp-repl-history
|
.lsp-repl-history
|
||||||
.terraform
|
.terraform
|
||||||
|
bin
|
||||||
build
|
build
|
||||||
|
env.yaml
|
||||||
node_modules
|
node_modules
|
||||||
out
|
out
|
||||||
sentinel.h
|
sentinel.h
|
||||||
financials/????-??/*
|
agent/agent
|
||||||
!financials/????-??/breakdown.txt
|
|
||||||
|
|
|
@ -8,6 +8,6 @@
|
||||||
* [Deploying your own instance of Riju](doc/selfhosting.md)
|
* [Deploying your own instance of Riju](doc/selfhosting.md)
|
||||||
|
|
||||||
If you'd like to request a new language, head to the [language support
|
If you'd like to request a new language, head to the [language support
|
||||||
meta-issue](https://github.com/raxod502/riju/issues/24) and add a
|
meta-issue](https://github.com/radian-software/riju/issues/24) and add
|
||||||
comment. Of course, if you actually want it to be added anytime soon,
|
a comment. Of course, if you actually want it to be added anytime
|
||||||
you should submit a pull request :)
|
soon, you should submit a pull request :)
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
# MIT License
|
# MIT License
|
||||||
|
|
||||||
Copyright (c) 2020 Radon Rosborough
|
Copyright (c) 2020–2022 [Radian LLC](https://radian.codes) and
|
||||||
|
contributors
|
||||||
|
|
||||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
of this software and associated documentation files (the "Software"), to deal
|
of this software and associated documentation files (the "Software"), to deal
|
||||||
|
|
50
Makefile
50
Makefile
|
@ -9,9 +9,9 @@ export
|
||||||
BUILD := build/$(T)/$(L)
|
BUILD := build/$(T)/$(L)
|
||||||
DEB := riju-$(T)-$(L).deb
|
DEB := riju-$(T)-$(L).deb
|
||||||
S3 := s3://$(S3_BUCKET)
|
S3 := s3://$(S3_BUCKET)
|
||||||
|
S3_CONFIG_PATH ?= config.json
|
||||||
S3_DEB := $(S3)/debs/$(DEB)
|
S3_DEB := $(S3)/debs/$(DEB)
|
||||||
S3_HASH := $(S3)/hashes/riju-$(T)-$(L)
|
S3_CONFIG := $(S3)/$(S3_CONFIG_PATH)
|
||||||
S3_CONFIG := $(S3)/config.json
|
|
||||||
|
|
||||||
ifneq ($(CMD),)
|
ifneq ($(CMD),)
|
||||||
C_CMD := -c '$(CMD)'
|
C_CMD := -c '$(CMD)'
|
||||||
|
@ -51,24 +51,25 @@ image: # I=<image> [L=<lang>] [NC=1] : Build a Docker image
|
||||||
ifeq ($(I),lang)
|
ifeq ($(I),lang)
|
||||||
@: $${L}
|
@: $${L}
|
||||||
node tools/build-lang-image.js --lang $(L)
|
node tools/build-lang-image.js --lang $(L)
|
||||||
else ifeq ($(I),ubuntu)
|
|
||||||
docker pull ubuntu:rolling
|
|
||||||
hash="$$(docker inspect ubuntu:rolling -f '{{ .Id }}' | sha1sum | awk '{ print $$1 }')"; echo "FROM ubuntu:rolling" | docker build --label riju.image-hash="$${hash}" -t riju:$(I) -
|
|
||||||
else ifneq (,$(filter $(I),admin ci))
|
else ifneq (,$(filter $(I),admin ci))
|
||||||
docker build . -f docker/$(I)/Dockerfile -t riju:$(I) $(NO_CACHE)
|
docker build . -f docker/$(I)/Dockerfile -t riju:$(I) $(NO_CACHE)
|
||||||
else
|
else
|
||||||
hash="$$(node tools/hash-dockerfile.js $(I) | grep .)"; docker build . -f docker/$(I)/Dockerfile -t riju:$(I) --label riju.image-hash="$${hash}" $(NO_CACHE)
|
docker build . -f docker/$(I)/Dockerfile -t riju:$(I) $(NO_CACHE)
|
||||||
endif
|
endif
|
||||||
|
|
||||||
VOLUME_MOUNT ?= $(PWD)
|
VOLUME_MOUNT ?= $(PWD)
|
||||||
|
|
||||||
|
# http
|
||||||
P1 ?= 6119
|
P1 ?= 6119
|
||||||
|
# https
|
||||||
P2 ?= 6120
|
P2 ?= 6120
|
||||||
|
# metrics
|
||||||
|
P3 ?= 6121
|
||||||
|
|
||||||
ifneq (,$(EE))
|
ifneq (,$(EE))
|
||||||
SHELL_PORTS := -p 0.0.0.0:$(P1):6119 -p 0.0.0.0:$(P2):6120
|
SHELL_PORTS := -p 0.0.0.0:$(P1):6119 -p 0.0.0.0:$(P2):6120 -p 0.0.0.0:$(P3):6121
|
||||||
else ifneq (,$(E))
|
else ifneq (,$(E))
|
||||||
SHELL_PORTS := -p 127.0.0.1:$(P1):6119 -p 127.0.0.1:$(P2):6120
|
SHELL_PORTS := -p 127.0.0.1:$(P1):6119 -p 127.0.0.1:$(P2):6120 -p 127.0.0.1:$(P3):6121
|
||||||
else
|
else
|
||||||
SHELL_PORTS :=
|
SHELL_PORTS :=
|
||||||
endif
|
endif
|
||||||
|
@ -81,25 +82,22 @@ else
|
||||||
LANG_TAG := $(I)
|
LANG_TAG := $(I)
|
||||||
endif
|
endif
|
||||||
|
|
||||||
IMAGE_HASH := "$$(docker inspect riju:$(LANG_TAG) -f '{{ index .Config.Labels "riju.image-hash" }}')"
|
|
||||||
WITH_IMAGE_HASH := -e RIJU_IMAGE_HASH=$(IMAGE_HASH)
|
|
||||||
|
|
||||||
shell: # I=<shell> [L=<lang>] [E[E]=1] [P1|P2=<port>] [CMD="<arg>..."] : Launch Docker image with shell
|
shell: # I=<shell> [L=<lang>] [E[E]=1] [P1|P2=<port>] [CMD="<arg>..."] : Launch Docker image with shell
|
||||||
@: $${I}
|
@: $${I}
|
||||||
ifneq (,$(filter $(I),admin ci))
|
ifneq (,$(filter $(I),admin ci))
|
||||||
@mkdir -p $(HOME)/.aws $(HOME)/.docker $(HOME)/.ssh $(HOME)/.terraform.d
|
@mkdir -p $(HOME)/.aws $(HOME)/.docker $(HOME)/.ssh $(HOME)/.terraform.d
|
||||||
docker run $(IT_ARG) --rm --hostname $(I) -v $(VOLUME_MOUNT):/src -v /var/cache/riju:/var/cache/riju -v /var/run/docker.sock:/var/run/docker.sock -v $(HOME)/.aws:/var/cache/riju/.aws -v $(HOME)/.docker:/var/cache/riju/.docker -v $(HOME)/.ssh:/var/cache/riju/.ssh -v $(HOME)/.terraform.d:/var/cache/riju/.terraform.d -e NI -e AWS_REGION -e AWS_ACCESS_KEY_ID -e AWS_SECRET_ACCESS_KEY -e DOCKER_REPO -e PUBLIC_DOCKER_REPO -e S3_BUCKET -e DOMAIN -e VOLUME_MOUNT=$(VOLUME_MOUNT) $(SHELL_PORTS) $(SHELL_ENV) $(WITH_IMAGE_HASH) --network host riju:$(I) $(BASH_CMD)
|
docker run $(IT_ARG) --rm --hostname $(I) -v $(VOLUME_MOUNT):/src -v /var/cache/riju:/var/cache/riju -v /var/run/docker.sock:/var/run/docker.sock -v $(HOME)/.aws:/var/cache/riju/.aws -v $(HOME)/.docker:/var/cache/riju/.docker -v $(HOME)/.ssh:/var/cache/riju/.ssh -v $(HOME)/.terraform.d:/var/cache/riju/.terraform.d -e NI -e AWS_REGION -e AWS_ACCESS_KEY_ID -e AWS_SECRET_ACCESS_KEY -e DOCKER_REPO -e PUBLIC_DOCKER_REPO -e S3_BUCKET -e DOMAIN -e VOLUME_MOUNT=$(VOLUME_MOUNT) $(SHELL_PORTS) $(SHELL_ENV) --network host riju:$(I) $(BASH_CMD)
|
||||||
else ifeq ($(I),app)
|
else ifeq ($(I),app)
|
||||||
docker run $(IT_ARG) --rm --hostname $(I) -v /var/cache/riju:/var/cache/riju -v /var/run/docker.sock:/var/run/docker.sock $(SHELL_PORTS) $(SHELL_ENV) $(WITH_IMAGE_HASH) riju:$(I) $(BASH_CMD)
|
docker run $(IT_ARG) --rm --hostname $(I) -v /var/cache/riju:/var/cache/riju -v /var/run/docker.sock:/var/run/docker.sock $(SHELL_PORTS) $(SHELL_ENV) riju:$(I) $(BASH_CMD)
|
||||||
else ifneq (,$(filter $(I),base lang))
|
else ifneq (,$(filter $(I),base lang))
|
||||||
ifeq ($(I),lang)
|
ifeq ($(I),lang)
|
||||||
@: $${L}
|
@: $${L}
|
||||||
endif
|
endif
|
||||||
docker run $(IT_ARG) --rm --hostname $(LANG_TAG) -v $(VOLUME_MOUNT):/src $(SHELL_PORTS) $(SHELL_ENV) $(WITH_IMAGE_HASH) riju:$(LANG_TAG) $(BASH_CMD)
|
docker run $(IT_ARG) --rm --hostname $(LANG_TAG) -v $(VOLUME_MOUNT):/src $(SHELL_PORTS) $(SHELL_ENV) riju:$(LANG_TAG) $(BASH_CMD)
|
||||||
else ifeq ($(I),runtime)
|
else ifeq ($(I),runtime)
|
||||||
docker run $(IT_ARG) --rm --hostname $(I) -v $(VOLUME_MOUNT):/src -v /var/cache/riju:/var/cache/riju -v /var/run/docker.sock:/var/run/docker.sock $(SHELL_PORTS) $(SHELL_ENV) $(WITH_IMAGE_HASH) riju:$(I) $(BASH_CMD)
|
docker run $(IT_ARG) --rm --hostname $(I) -v $(VOLUME_MOUNT):/src -v /var/cache/riju:/var/cache/riju -v /var/run/docker.sock:/var/run/docker.sock $(SHELL_PORTS) $(SHELL_ENV) riju:$(I) $(BASH_CMD)
|
||||||
else
|
else
|
||||||
docker run $(IT_ARG) --rm --hostname $(I) -v $(VOLUME_MOUNT):/src $(SHELL_PORTS) $(SHELL_ENV) $(WITH_IMAGE_HASH) riju:$(I) $(BASH_CMD)
|
docker run $(IT_ARG) --rm --hostname $(I) -v $(VOLUME_MOUNT):/src $(SHELL_PORTS) $(SHELL_ENV) riju:$(I) $(BASH_CMD)
|
||||||
endif
|
endif
|
||||||
|
|
||||||
ecr: # Authenticate to ECR (temporary credentials)
|
ecr: # Authenticate to ECR (temporary credentials)
|
||||||
|
@ -213,7 +211,7 @@ lsp: # L=<lang|cmd> : Run LSP REPL for language or custom command line
|
||||||
|
|
||||||
### Fetch artifacts from registries
|
### Fetch artifacts from registries
|
||||||
|
|
||||||
PUBLIC_DOCKER_REPO_PULL ?= public.ecr.aws/raxod502/riju
|
PUBLIC_DOCKER_REPO_PULL ?= public.ecr.aws/radian-software/riju
|
||||||
|
|
||||||
sync-ubuntu: # Pull Riju Ubuntu image from public Docker registry
|
sync-ubuntu: # Pull Riju Ubuntu image from public Docker registry
|
||||||
docker pull $(PUBLIC_DOCKER_REPO_PULL):ubuntu
|
docker pull $(PUBLIC_DOCKER_REPO_PULL):ubuntu
|
||||||
|
@ -237,21 +235,13 @@ undeploy: # Pull latest deployment config from S3
|
||||||
|
|
||||||
push: # I=<image> : Push Riju image to Docker registry
|
push: # I=<image> : Push Riju image to Docker registry
|
||||||
@: $${I} $${DOCKER_REPO}
|
@: $${I} $${DOCKER_REPO}
|
||||||
docker tag riju:$(I) $(DOCKER_REPO):$(I)-$(IMAGE_HASH)
|
|
||||||
docker push $(DOCKER_REPO):$(I)-$(IMAGE_HASH)
|
|
||||||
ifeq ($(I),ubuntu)
|
|
||||||
docker tag riju:$(I) $(PUBLIC_DOCKER_REPO):$(I)
|
|
||||||
docker push $(PUBLIC_DOCKER_REPO):$(I)
|
|
||||||
endif
|
|
||||||
docker tag riju:$(I) $(DOCKER_REPO):$(I)
|
docker tag riju:$(I) $(DOCKER_REPO):$(I)
|
||||||
docker push $(DOCKER_REPO):$(I)
|
docker push $(DOCKER_REPO):$(I)
|
||||||
|
|
||||||
upload: # L=<lang> T=<type> : Upload .deb to S3
|
upload: # L=<lang> T=<type> : Upload .deb to S3
|
||||||
@: $${L} $${T} $${S3_BUCKET}
|
@: $${L} $${T} $${S3_BUCKET}
|
||||||
tools/ensure-deb-compressed.bash
|
tools/ensure-deb-compressed.bash
|
||||||
aws s3 rm --recursive $(S3_HASH)
|
|
||||||
aws s3 cp $(BUILD)/$(DEB) $(S3_DEB)
|
aws s3 cp $(BUILD)/$(DEB) $(S3_DEB)
|
||||||
hash="$$(dpkg-deb -f $(BUILD)/$(DEB) Riju-Script-Hash | grep .)"; aws s3 cp - "$(S3_HASH)/$${hash}" < /dev/null
|
|
||||||
|
|
||||||
deploy-config: # Generate deployment config file
|
deploy-config: # Generate deployment config file
|
||||||
node tools/generate-deploy-config.js
|
node tools/generate-deploy-config.js
|
||||||
|
@ -282,11 +272,13 @@ fmt: fmt-c fmt-go fmt-python fmt-terraform fmt-web # Format all code
|
||||||
|
|
||||||
### Infrastructure
|
### Infrastructure
|
||||||
|
|
||||||
packer-web: supervisor # Build and publish a new webserver AMI
|
packer: supervisor # Build and publish a new webserver AMI
|
||||||
tools/packer-build-web.bash
|
tools/packer-build.bash
|
||||||
|
|
||||||
packer-ci: # Build and publish a new CI AMI
|
deploy-alerts: # Deploy alerting configuration to Grafana Cloud
|
||||||
tools/packer-build-ci.bash
|
envsubst < grafana/alertmanager.yaml > grafana/alertmanager.yaml.out
|
||||||
|
cortextool rules load grafana/alerts.yaml --address=https://$(GRAFANA_PROMETHEUS_HOSTNAME) --id=$(GRAFANA_PROMETHEUS_USERNAME) --key=$(GRAFANA_API_KEY)
|
||||||
|
cortextool alertmanager load grafana/alertmanager.yaml.out --address=https://alertmanager-us-central1.grafana.net --id=$(GRAFANA_ALERTMANAGER_USERNAME) --key=$(GRAFANA_API_KEY)
|
||||||
|
|
||||||
### Miscellaneous
|
### Miscellaneous
|
||||||
|
|
||||||
|
|
16
README.md
16
README.md
|
@ -6,18 +6,18 @@ or compiling [INTERCAL](https://en.wikipedia.org/wiki/INTERCAL) code.
|
||||||
|
|
||||||
Check it out at <https://riju.codes>!
|
Check it out at <https://riju.codes>!
|
||||||
|
|
||||||
Service uptime available at <https://riju.statuspage.io/>.
|
Service uptime available at <https://radian.statuspage.io/>.
|
||||||
|
|
||||||
## Is it free?
|
## Is it free?
|
||||||
|
|
||||||
Riju will always be free for everyone. I pay for the hosting costs
|
Riju will always be free for everyone. I pay for the hosting costs out
|
||||||
myself.
|
of the business account of Radian LLC, which is funded by donations
|
||||||
|
and my personal savings. If you would like to help keep Riju online
|
||||||
|
and see more projects like it, there are a few donation methods
|
||||||
|
available in the "Sponsor this project" sidebar on GitHub.
|
||||||
|
|
||||||
A number of people have asked me if they can donate to help keep Riju
|
All financial records for Radian LLC are made [publicly
|
||||||
online. In response, I have set up a few methods, which you can see in
|
available](https://github.com/radian-software/financials).
|
||||||
the "Sponsor this project" sidebar on GitHub. All donations will be
|
|
||||||
used solely to cover hosting costs, and any surplus will be donated to
|
|
||||||
the [Electronic Frontier Foundation](https://www.eff.org/).
|
|
||||||
|
|
||||||
## Is it safe?
|
## Is it safe?
|
||||||
|
|
||||||
|
|
|
@ -1,8 +1,8 @@
|
||||||
# Reporting a security issue
|
# Reporting a security issue
|
||||||
|
|
||||||
Please contact me at
|
Please contact me at
|
||||||
[radon.neon@gmail.com](mailto:radon.neon@gmail.com) if you find any
|
[security+riju@radian.codes](mailto:security+riju@radian.codes) if you
|
||||||
way to:
|
find any way to:
|
||||||
|
|
||||||
* Take down Riju without using a large number of concurrent sessions.
|
* Take down Riju without using a large number of concurrent sessions.
|
||||||
* View or interfere with another user's session.
|
* View or interfere with another user's session.
|
||||||
|
|
|
@ -0,0 +1,8 @@
|
||||||
|
module github.com/radian-software/riju/agent
|
||||||
|
|
||||||
|
go 1.18
|
||||||
|
|
||||||
|
require (
|
||||||
|
github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510 // indirect
|
||||||
|
github.com/gorilla/websocket v1.5.0 // indirect
|
||||||
|
)
|
|
@ -0,0 +1,4 @@
|
||||||
|
github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510 h1:El6M4kTTCOh6aBiKaUGG7oYTSPP8MxqL4YI3kZKwcP4=
|
||||||
|
github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510/go.mod h1:pupxD2MaaD3pAXIBCelhxNneeOaAeabZDe5s4K6zSpQ=
|
||||||
|
github.com/gorilla/websocket v1.5.0 h1:PPwGk2jz7EePpoHN/+ClbZu8SPxiqlu12wZP/3sWmnc=
|
||||||
|
github.com/gorilla/websocket v1.5.0/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE=
|
|
@ -0,0 +1,30 @@
|
||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"io"
|
||||||
|
"log"
|
||||||
|
)
|
||||||
|
|
||||||
|
func logWarn(err error) {
|
||||||
|
log.Println(err.Error())
|
||||||
|
}
|
||||||
|
|
||||||
|
func logWarnf(format string, arg ...interface{}) {
|
||||||
|
logWarn(fmt.Errorf(format, arg...))
|
||||||
|
}
|
||||||
|
|
||||||
|
func logError(err error) {
|
||||||
|
log.Println(err.Error())
|
||||||
|
}
|
||||||
|
|
||||||
|
func logErrorf(format string, arg ...interface{}) {
|
||||||
|
logError(fmt.Errorf(format, arg...))
|
||||||
|
}
|
||||||
|
|
||||||
|
func tryClose(obj io.Closer, objName string) {
|
||||||
|
err := obj.Close()
|
||||||
|
if err != nil {
|
||||||
|
logErrorf("error closing %s: %w", objName, err)
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,225 @@
|
||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
"fmt"
|
||||||
|
"net/http"
|
||||||
|
"os"
|
||||||
|
"os/exec"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/google/shlex"
|
||||||
|
"github.com/gorilla/websocket"
|
||||||
|
)
|
||||||
|
|
||||||
|
type clientMessage struct {
|
||||||
|
// "stdin"
|
||||||
|
Event string `json:"event"`
|
||||||
|
// contents of stdin
|
||||||
|
Data []byte `json:"data,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type serverMessage struct {
|
||||||
|
// "start", "stdout", "stderr", "exit", "warn", "error"
|
||||||
|
Event string `json:"event"`
|
||||||
|
// contents of stdout/stderr
|
||||||
|
Data []byte `json:"data,omitempty"`
|
||||||
|
// error message
|
||||||
|
Text string `json:"text,omitempty"`
|
||||||
|
// exit status
|
||||||
|
ExitStatus *int `json:"exitStatus,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
var upgrader = websocket.Upgrader{}
|
||||||
|
|
||||||
|
func closeWs(ms *ManagedWebsocket) {
|
||||||
|
ms.CloseChan <- struct{}{}
|
||||||
|
}
|
||||||
|
|
||||||
|
func send(ms *ManagedWebsocket, msg *serverMessage) {
|
||||||
|
data, err := json.Marshal(msg)
|
||||||
|
if err != nil {
|
||||||
|
logErrorf("marshaling message: %w", err)
|
||||||
|
closeWs(ms)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
ms.OutgoingChan <- data
|
||||||
|
}
|
||||||
|
|
||||||
|
func fatal(ms *ManagedWebsocket, err error) {
|
||||||
|
send(ms, &serverMessage{
|
||||||
|
Event: "fatal",
|
||||||
|
Text: err.Error(),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func fatalf(ms *ManagedWebsocket, format string, arg ...interface{}) {
|
||||||
|
fatal(ms, fmt.Errorf(format, arg...))
|
||||||
|
}
|
||||||
|
|
||||||
|
func warn(ms *ManagedWebsocket, err error) {
|
||||||
|
send(ms, &serverMessage{
|
||||||
|
Event: "warn",
|
||||||
|
Text: err.Error(),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func warnf(ms *ManagedWebsocket, format string, arg ...interface{}) {
|
||||||
|
warn(ms, fmt.Errorf(format, arg...))
|
||||||
|
}
|
||||||
|
|
||||||
|
func getCommandPrefix() []string {
|
||||||
|
prefix := os.Getenv("RIJU_AGENT_COMMAND_PREFIX")
|
||||||
|
if prefix == "" {
|
||||||
|
logErrorf("must specify RIJU_AGENT_COMMAND_PREFIX for security reasons")
|
||||||
|
os.Exit(1)
|
||||||
|
}
|
||||||
|
if prefix == "0" {
|
||||||
|
return []string{}
|
||||||
|
}
|
||||||
|
list, err := shlex.Split(prefix)
|
||||||
|
if err != nil {
|
||||||
|
logErrorf("parsing RIJU_AGENT_COMMAND_PREFIX: %w", err)
|
||||||
|
os.Exit(1)
|
||||||
|
}
|
||||||
|
return list
|
||||||
|
}
|
||||||
|
|
||||||
|
var CommandPrefix = getCommandPrefix()
|
||||||
|
|
||||||
|
// https://github.com/gorilla/websocket/blob/76ecc29eff79f0cedf70c530605e486fc32131d1/examples/command/main.go
|
||||||
|
func handler(w http.ResponseWriter, r *http.Request) {
|
||||||
|
// Upgrade http connection to websocket
|
||||||
|
ws, err := upgrader.Upgrade(w, r, nil)
|
||||||
|
if err != nil {
|
||||||
|
logErrorf("upgrading connection: %w", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
// Set up channels to handle incoming and outgoing websocket
|
||||||
|
// messages more conveniently, and also to handle closing the
|
||||||
|
// websocket on error or when we ask.
|
||||||
|
ms := &ManagedWebsocket{
|
||||||
|
Socket: ws,
|
||||||
|
|
||||||
|
MessageType: websocket.TextMessage,
|
||||||
|
PingPeriod: 5 * time.Second,
|
||||||
|
ReadTimeout: 10 * time.Second,
|
||||||
|
WriteTimeout: 10 * time.Second,
|
||||||
|
}
|
||||||
|
ms.Init()
|
||||||
|
// Ensure that websocket will be closed eventually when we
|
||||||
|
// exit.
|
||||||
|
defer closeWs(ms)
|
||||||
|
// Parse request query parameters; do this after upgrading to
|
||||||
|
// websocket so that we can send errors back on the websocket
|
||||||
|
// which is easier for clients to parse
|
||||||
|
cmdline := r.URL.Query()["cmdline"]
|
||||||
|
if len(cmdline) == 0 {
|
||||||
|
fatalf(ms, "cmdline query parameter missing")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
cmdline = append(CommandPrefix, cmdline...)
|
||||||
|
binary, err := exec.LookPath(cmdline[0])
|
||||||
|
if err != nil {
|
||||||
|
fatalf(ms, "searching for executable: %w", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
// Spawn subprocess
|
||||||
|
mp, err := NewManagedProcess(binary, cmdline, nil)
|
||||||
|
if err != nil {
|
||||||
|
fatalf(ms, "spawning process: %w", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
// Ensure eventual process termination
|
||||||
|
defer func() {
|
||||||
|
mp.CloseChan <- struct{}{}
|
||||||
|
}()
|
||||||
|
// Handle received messages from client
|
||||||
|
go func() {
|
||||||
|
for data := range ms.IncomingChan {
|
||||||
|
msg := clientMessage{}
|
||||||
|
err := json.Unmarshal(data, &msg)
|
||||||
|
if err != nil {
|
||||||
|
warnf(ms, "parsing json: %w", err)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
switch msg.Event {
|
||||||
|
case "stdin":
|
||||||
|
mp.StdinChan <- msg.Data
|
||||||
|
default:
|
||||||
|
logWarnf("received unknown event type %s", msg.Event)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
// Proxy stdout and stderr from subprocess
|
||||||
|
go func() {
|
||||||
|
for data := range mp.StdoutChan {
|
||||||
|
msg, err := json.Marshal(&serverMessage{
|
||||||
|
Event: "stdout",
|
||||||
|
Data: data,
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
warnf(ms, "wrapping stdout in json: %w", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
ms.OutgoingChan <- msg
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
go func() {
|
||||||
|
for data := range mp.StderrChan {
|
||||||
|
msg, err := json.Marshal(&serverMessage{
|
||||||
|
Event: "stderr",
|
||||||
|
Data: data,
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
warnf(ms, "wrapping stderr in json: %w", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
ms.OutgoingChan <- msg
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
// Send info about process exit status
|
||||||
|
exitChan2 := make(chan struct{}, 16)
|
||||||
|
go func() {
|
||||||
|
for status := range mp.ExitChan {
|
||||||
|
exitChan2 <- struct{}{}
|
||||||
|
code := status.ExitCode()
|
||||||
|
send(ms, &serverMessage{
|
||||||
|
Event: "exit",
|
||||||
|
ExitStatus: &code,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
// Wait until one of subprocess or websocket exits. The other
|
||||||
|
// one will be cleaned up on return.
|
||||||
|
select {
|
||||||
|
case <-exitChan2:
|
||||||
|
case <-ms.ClosedChan:
|
||||||
|
}
|
||||||
|
// Wait a bit to send any pending messages before closing the
|
||||||
|
// connection.
|
||||||
|
time.Sleep(1 * time.Second)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
port := os.Getenv("RIJU_AGENT_PORT")
|
||||||
|
if port == "" {
|
||||||
|
port = "869"
|
||||||
|
}
|
||||||
|
host := os.Getenv("RIJU_AGENT_HOST")
|
||||||
|
if host == "" {
|
||||||
|
host = "0.0.0.0"
|
||||||
|
}
|
||||||
|
fmt.Printf("Listening on http://%s:%s\n", host, port)
|
||||||
|
mux := http.NewServeMux()
|
||||||
|
mux.HandleFunc("/exec", handler)
|
||||||
|
mux.HandleFunc("/health", func(w http.ResponseWriter, r *http.Request) {
|
||||||
|
w.WriteHeader(http.StatusOK)
|
||||||
|
})
|
||||||
|
err := http.ListenAndServe(fmt.Sprintf("%s:%s", host, port), mux)
|
||||||
|
if err != nil {
|
||||||
|
logError(err)
|
||||||
|
os.Exit(1)
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,164 @@
|
||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"os"
|
||||||
|
"syscall"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
type managedProcess struct {
|
||||||
|
proc *os.Process
|
||||||
|
|
||||||
|
stdinRead *os.File
|
||||||
|
stdinWrite *os.File
|
||||||
|
stdoutRead *os.File
|
||||||
|
stdoutWrite *os.File
|
||||||
|
stderrRead *os.File
|
||||||
|
stderrWrite *os.File
|
||||||
|
|
||||||
|
internalExitChan chan struct{}
|
||||||
|
|
||||||
|
StdinChan chan []byte
|
||||||
|
StdoutChan chan []byte
|
||||||
|
StderrChan chan []byte
|
||||||
|
ExitChan chan *os.ProcessState
|
||||||
|
CloseChan chan struct{}
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewManagedProcess(name string, argv []string, attr *os.ProcAttr) (*managedProcess, error) {
|
||||||
|
mp := &managedProcess{
|
||||||
|
internalExitChan: make(chan struct{}, 16),
|
||||||
|
|
||||||
|
StdinChan: make(chan []byte, 16),
|
||||||
|
StdoutChan: make(chan []byte, 16),
|
||||||
|
StderrChan: make(chan []byte, 16),
|
||||||
|
ExitChan: make(chan *os.ProcessState, 16),
|
||||||
|
CloseChan: make(chan struct{}, 16),
|
||||||
|
}
|
||||||
|
done := false
|
||||||
|
go mp.handleClose()
|
||||||
|
defer func() {
|
||||||
|
if !done {
|
||||||
|
mp.CloseChan <- struct{}{}
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
var err error
|
||||||
|
mp.stdinRead, mp.stdinWrite, err = os.Pipe()
|
||||||
|
if err != nil {
|
||||||
|
return mp, fmt.Errorf("creating stdin pipe: %w", err)
|
||||||
|
}
|
||||||
|
mp.stdoutRead, mp.stdoutWrite, err = os.Pipe()
|
||||||
|
if err != nil {
|
||||||
|
return mp, fmt.Errorf("creating stdout pipe: %w", err)
|
||||||
|
}
|
||||||
|
mp.stderrRead, mp.stderrWrite, err = os.Pipe()
|
||||||
|
if err != nil {
|
||||||
|
return mp, fmt.Errorf("creating stderr pipe: %w", err)
|
||||||
|
}
|
||||||
|
newAttr := &os.ProcAttr{}
|
||||||
|
if attr != nil {
|
||||||
|
*newAttr = *attr
|
||||||
|
}
|
||||||
|
if len(newAttr.Files) < 3 {
|
||||||
|
newAttr.Files = append(newAttr.Files, make([]*os.File, 3-len(newAttr.Files))...)
|
||||||
|
newAttr.Files[0] = mp.stdinRead
|
||||||
|
newAttr.Files[1] = mp.stdoutWrite
|
||||||
|
newAttr.Files[2] = mp.stderrWrite
|
||||||
|
}
|
||||||
|
mp.proc, err = os.StartProcess(name, argv, newAttr)
|
||||||
|
if err != nil {
|
||||||
|
return mp, fmt.Errorf("spawning process: %w", err)
|
||||||
|
}
|
||||||
|
go mp.handleWait()
|
||||||
|
go mp.handleInput(mp.StdinChan, mp.stdinWrite, "stdin")
|
||||||
|
go mp.handleOutput(mp.StdoutChan, mp.stdoutRead, "stdout")
|
||||||
|
go mp.handleOutput(mp.StderrChan, mp.stderrRead, "stderr")
|
||||||
|
done = true
|
||||||
|
return mp, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (mp *managedProcess) handleInput(ch chan []byte, f *os.File, name string) {
|
||||||
|
for data := range ch {
|
||||||
|
_, err := f.Write(data)
|
||||||
|
if err != nil {
|
||||||
|
// Likely stdin closed by subprocess, this is normal
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (mp *managedProcess) handleOutput(ch chan []byte, f *os.File, name string) {
|
||||||
|
for {
|
||||||
|
buf := make([]byte, 1024)
|
||||||
|
nr, err := f.Read(buf)
|
||||||
|
if err != nil {
|
||||||
|
// Likely stdout/stderr closed by subprocess,
|
||||||
|
// this is normal
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if nr == 0 {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
ch <- buf[:nr]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (mp *managedProcess) handleWait() {
|
||||||
|
s, err := mp.proc.Wait()
|
||||||
|
if err != nil {
|
||||||
|
logErrorf("waiting on process: %w", err)
|
||||||
|
}
|
||||||
|
mp.internalExitChan <- struct{}{}
|
||||||
|
mp.ExitChan <- s
|
||||||
|
}
|
||||||
|
|
||||||
|
func (mp *managedProcess) killProc() {
|
||||||
|
// See if process has already exited or is about to
|
||||||
|
select {
|
||||||
|
case <-mp.internalExitChan:
|
||||||
|
return
|
||||||
|
case <-time.NewTimer(500 * time.Millisecond).C:
|
||||||
|
//
|
||||||
|
}
|
||||||
|
// Try killing the process by closing stdin
|
||||||
|
mp.stdinWrite.Close()
|
||||||
|
select {
|
||||||
|
case <-mp.internalExitChan:
|
||||||
|
return
|
||||||
|
case <-time.NewTimer(500 * time.Millisecond).C:
|
||||||
|
//
|
||||||
|
}
|
||||||
|
// Try killing the process with SIGTERM, SIGINT, then
|
||||||
|
// finally SIGKILL
|
||||||
|
for _, sig := range []os.Signal{syscall.SIGTERM, syscall.SIGINT, syscall.SIGKILL} {
|
||||||
|
err := mp.proc.Signal(sig)
|
||||||
|
if err != nil {
|
||||||
|
logErrorf("sending %s to child: %w", sig.String(), err)
|
||||||
|
}
|
||||||
|
select {
|
||||||
|
case <-mp.internalExitChan:
|
||||||
|
return
|
||||||
|
case <-time.NewTimer(500 * time.Millisecond).C:
|
||||||
|
//
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// We are unable to kill the process
|
||||||
|
logErrorf("unable to kill child process (pid %d)", mp.proc.Pid)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (mp *managedProcess) handleClose() {
|
||||||
|
<-mp.CloseChan
|
||||||
|
for _, p := range []*os.File{
|
||||||
|
mp.stdinRead, mp.stdinWrite,
|
||||||
|
mp.stdoutRead, mp.stdoutWrite,
|
||||||
|
mp.stderrRead, mp.stderrWrite,
|
||||||
|
} {
|
||||||
|
if p != nil {
|
||||||
|
p.Close()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if mp.proc != nil {
|
||||||
|
//
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,107 @@
|
||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/gorilla/websocket"
|
||||||
|
)
|
||||||
|
|
||||||
|
type ManagedWebsocket struct {
|
||||||
|
Socket *websocket.Conn
|
||||||
|
|
||||||
|
MessageType int
|
||||||
|
PingPeriod time.Duration
|
||||||
|
ReadTimeout time.Duration
|
||||||
|
WriteTimeout time.Duration
|
||||||
|
|
||||||
|
IncomingChan chan []byte
|
||||||
|
OutgoingChan chan []byte
|
||||||
|
CloseChan chan struct{}
|
||||||
|
ClosedChan chan struct{}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *ManagedWebsocket) handleIncoming() {
|
||||||
|
pongChan := make(chan struct{}, 16)
|
||||||
|
m.Socket.SetPongHandler(func(string) error {
|
||||||
|
pongChan <- struct{}{}
|
||||||
|
return nil
|
||||||
|
})
|
||||||
|
msgChan := make(chan []byte, 16)
|
||||||
|
go func() {
|
||||||
|
defer close(msgChan)
|
||||||
|
for {
|
||||||
|
msgtype, data, err := m.Socket.ReadMessage()
|
||||||
|
if err != nil {
|
||||||
|
m.Socket.Close()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if msgtype != m.MessageType {
|
||||||
|
logWarnf("ignoring message of unexpected type %d", msgtype)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
msgChan <- data
|
||||||
|
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
for {
|
||||||
|
m.Socket.SetReadDeadline(time.Now().Add(m.ReadTimeout))
|
||||||
|
var msgtype int
|
||||||
|
var msgdata []byte
|
||||||
|
select {
|
||||||
|
case <-pongChan:
|
||||||
|
msgtype = websocket.PongMessage
|
||||||
|
case data := <-msgChan:
|
||||||
|
msgtype = m.MessageType
|
||||||
|
msgdata = data
|
||||||
|
}
|
||||||
|
if msgtype != m.MessageType {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
m.IncomingChan <- msgdata
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *ManagedWebsocket) handleOutgoing() {
|
||||||
|
pingTicker := time.NewTicker(m.PingPeriod)
|
||||||
|
defer pingTicker.Stop()
|
||||||
|
defer func() {
|
||||||
|
m.ClosedChan <- struct{}{}
|
||||||
|
}()
|
||||||
|
for {
|
||||||
|
var msgtype int
|
||||||
|
var msgdata []byte
|
||||||
|
select {
|
||||||
|
case <-pingTicker.C:
|
||||||
|
msgtype = websocket.PingMessage
|
||||||
|
msgdata = []byte{}
|
||||||
|
case data := <-m.OutgoingChan:
|
||||||
|
msgtype = m.MessageType
|
||||||
|
msgdata = data
|
||||||
|
case <-m.CloseChan:
|
||||||
|
msgtype = websocket.CloseMessage
|
||||||
|
msgdata = websocket.FormatCloseMessage(websocket.CloseNormalClosure, "")
|
||||||
|
}
|
||||||
|
wd := time.Now().Add(m.WriteTimeout)
|
||||||
|
m.Socket.SetWriteDeadline(wd)
|
||||||
|
err := m.Socket.WriteMessage(msgtype, msgdata)
|
||||||
|
if err != nil {
|
||||||
|
m.Socket.Close()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if msgtype == websocket.CloseMessage {
|
||||||
|
time.Sleep(wd.Sub(time.Now()))
|
||||||
|
m.Socket.Close()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *ManagedWebsocket) Init() {
|
||||||
|
m.IncomingChan = make(chan []byte, 16)
|
||||||
|
m.OutgoingChan = make(chan []byte, 16)
|
||||||
|
m.CloseChan = make(chan struct{}, 16)
|
||||||
|
m.ClosedChan = make(chan struct{}, 16)
|
||||||
|
|
||||||
|
go m.handleIncoming()
|
||||||
|
go m.handleOutgoing()
|
||||||
|
}
|
|
@ -0,0 +1,209 @@
|
||||||
|
import * as k8sClient from "@kubernetes/client-node";
|
||||||
|
import lodash from "lodash";
|
||||||
|
|
||||||
|
const kubeconfig = new k8sClient.KubeConfig();
|
||||||
|
kubeconfig.loadFromDefault();
|
||||||
|
|
||||||
|
const k8s = kubeconfig.makeApiClient(k8sClient.CoreV1Api);
|
||||||
|
|
||||||
|
export function watchPods() {
|
||||||
|
const callbacks = {};
|
||||||
|
const pods = {};
|
||||||
|
|
||||||
|
// https://github.com/kubernetes-client/javascript/blob/1f76ee10c54e33a998abb4686488ccff4285366a/examples/typescript/informer/informer.ts
|
||||||
|
//
|
||||||
|
// The watch functionality seems to be wholly undocumented. Copy,
|
||||||
|
// paste, and pray.
|
||||||
|
const informer = k8sClient.makeInformer(
|
||||||
|
kubeconfig,
|
||||||
|
"/api/v1/namespaces/riju-user/pods",
|
||||||
|
() => k8s.listNamespacedPod("riju-user")
|
||||||
|
);
|
||||||
|
|
||||||
|
for (const event of ["add", "update", "delete"]) {
|
||||||
|
informer.on(event, (pod) => {
|
||||||
|
if (pod.metadata.name in callbacks) {
|
||||||
|
callbacks[pod.metadata.name](event, pod);
|
||||||
|
}
|
||||||
|
pods[pod.metadata.name] = pod;
|
||||||
|
if (event == "delete") {
|
||||||
|
delete callbacks[pod.metadata.name];
|
||||||
|
delete pods[pod.metadata.name];
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
informer.on("error", (err) => {
|
||||||
|
console.error(err);
|
||||||
|
setTimeout(() => informer.start(), 5000);
|
||||||
|
});
|
||||||
|
informer.start();
|
||||||
|
|
||||||
|
return {
|
||||||
|
setCallback: (podName, callback) => {
|
||||||
|
callbacks[podName] = callback;
|
||||||
|
if (podName in pods) {
|
||||||
|
callback("add", pods[podName]);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function listUserSessions() {
|
||||||
|
return (await k8s.listNamespacedPod("riju-user")).body.items.map((pod) => ({
|
||||||
|
podName: pod.metadata.name,
|
||||||
|
sessionID: pod.metadata.labels["riju.codes/user-session-id"],
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function createUserSession({
|
||||||
|
watcher,
|
||||||
|
sessionID,
|
||||||
|
langConfig,
|
||||||
|
revisions,
|
||||||
|
}) {
|
||||||
|
const pod = (
|
||||||
|
await k8s.createNamespacedPod("riju-user", {
|
||||||
|
metadata: {
|
||||||
|
name: `riju-user-session-${sessionID}`,
|
||||||
|
labels: {
|
||||||
|
"riju.codes/user-session-id": sessionID,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
spec: {
|
||||||
|
volumes: [
|
||||||
|
{
|
||||||
|
name: "minio-config",
|
||||||
|
secret: {
|
||||||
|
secretName: "minio-user-login",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "riju-bin",
|
||||||
|
emptyDir: {},
|
||||||
|
},
|
||||||
|
],
|
||||||
|
imagePullSecrets: [
|
||||||
|
{
|
||||||
|
name: "registry-user-login",
|
||||||
|
},
|
||||||
|
],
|
||||||
|
initContainers: [
|
||||||
|
{
|
||||||
|
name: "download",
|
||||||
|
image: "minio/mc:RELEASE.2022-12-13T00-23-28Z",
|
||||||
|
resources: {},
|
||||||
|
command: ["sh", "-c"],
|
||||||
|
args: [
|
||||||
|
`mkdir -p /root/.mc && cp -LT /mc/config.json /root/.mc/config.json &&` +
|
||||||
|
`mc cp riju/agent/${revisions.agent} /riju-bin/agent && chmod +x /riju-bin/agent &&` +
|
||||||
|
`mc cp riju/ptyify/${revisions.ptyify} /riju-bin/ptyify && chmod +x /riju-bin/ptyify`,
|
||||||
|
],
|
||||||
|
volumeMounts: [
|
||||||
|
{
|
||||||
|
name: "minio-config",
|
||||||
|
mountPath: "/mc",
|
||||||
|
readOnly: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "riju-bin",
|
||||||
|
mountPath: "/riju-bin",
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
containers: [
|
||||||
|
{
|
||||||
|
name: "session",
|
||||||
|
image: `localhost:30999/riju-lang:${langConfig.id}-${revisions.langImage}`,
|
||||||
|
resources: {
|
||||||
|
requests: {},
|
||||||
|
limits: {
|
||||||
|
cpu: "1000m",
|
||||||
|
memory: "4Gi",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
command: ["/riju-bin/agent"],
|
||||||
|
env: [
|
||||||
|
{
|
||||||
|
name: "RIJU_AGENT_COMMAND_PREFIX",
|
||||||
|
value: "runuser -u riju --",
|
||||||
|
},
|
||||||
|
],
|
||||||
|
securityContext: {
|
||||||
|
runAsUser: 0,
|
||||||
|
},
|
||||||
|
startupProbe: {
|
||||||
|
httpGet: {
|
||||||
|
path: "/health",
|
||||||
|
port: 869,
|
||||||
|
scheme: "HTTP",
|
||||||
|
},
|
||||||
|
failureThreshold: 30,
|
||||||
|
initialDelaySeconds: 0,
|
||||||
|
periodSeconds: 1,
|
||||||
|
successThreshold: 1,
|
||||||
|
timeoutSeconds: 2,
|
||||||
|
},
|
||||||
|
readinessProbe: {
|
||||||
|
httpGet: {
|
||||||
|
path: "/health",
|
||||||
|
port: 869,
|
||||||
|
scheme: "HTTP",
|
||||||
|
},
|
||||||
|
failureThreshold: 1,
|
||||||
|
initialDelaySeconds: 2,
|
||||||
|
periodSeconds: 10,
|
||||||
|
successThreshold: 1,
|
||||||
|
timeoutSeconds: 2,
|
||||||
|
},
|
||||||
|
livenessProbe: {
|
||||||
|
httpGet: {
|
||||||
|
path: "/health",
|
||||||
|
port: 869,
|
||||||
|
scheme: "HTTP",
|
||||||
|
},
|
||||||
|
failureThreshold: 3,
|
||||||
|
initialDelaySeconds: 2,
|
||||||
|
periodSeconds: 10,
|
||||||
|
successThreshold: 1,
|
||||||
|
timeoutSeconds: 2,
|
||||||
|
},
|
||||||
|
volumeMounts: [
|
||||||
|
{
|
||||||
|
name: "riju-bin",
|
||||||
|
mountPath: "/riju-bin",
|
||||||
|
readOnly: true,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
restartPolicy: "Never",
|
||||||
|
},
|
||||||
|
})
|
||||||
|
).body;
|
||||||
|
const podIP = await new Promise((resolve, reject) => {
|
||||||
|
setTimeout(() => reject("timed out"), 5 * 60 * 1000);
|
||||||
|
watcher.setCallback(pod.metadata.name, (event, pod) => {
|
||||||
|
if (event == "delete") {
|
||||||
|
reject(new Error("pod was deleted"));
|
||||||
|
} else if (pod.status.phase === "Failed") {
|
||||||
|
reject(new Error("pod status became Failed"));
|
||||||
|
} else if (
|
||||||
|
pod.status.podIP &&
|
||||||
|
lodash.every(pod.status.containerStatuses, (status) => status.ready)
|
||||||
|
) {
|
||||||
|
resolve(pod.status.podIP);
|
||||||
|
} else {
|
||||||
|
console.log(event, JSON.stringify(pod.status, null, 2));
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
return podIP;
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function deleteUserSessions(sessionsToDelete) {
|
||||||
|
for (const { podName } of sessionsToDelete) {
|
||||||
|
await k8s.deleteNamespacedPod(podName, "riju-user");
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,5 +1,4 @@
|
||||||
import fsOrig, { promises as fs } from "fs";
|
import fsOrig from "fs";
|
||||||
import path from "path";
|
|
||||||
|
|
||||||
import debounce from "debounce";
|
import debounce from "debounce";
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,80 @@
|
||||||
|
import { spawn } from "child_process";
|
||||||
|
import { promises as fs } from "fs";
|
||||||
|
import process from "process";
|
||||||
|
|
||||||
|
import { readLangConfig } from "../lib/yaml.js";
|
||||||
|
import * as k8s from "./k8s.js";
|
||||||
|
import { getUUID, quote } from "./util.js";
|
||||||
|
|
||||||
|
function die(msg) {
|
||||||
|
console.error(msg);
|
||||||
|
process.exit(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
async function main() {
|
||||||
|
const sandboxScript = await fs.readFile("backend/sandbox.bash", "utf-8");
|
||||||
|
const lang = process.env.L;
|
||||||
|
if (!lang) {
|
||||||
|
die("environment variable unset: $L");
|
||||||
|
}
|
||||||
|
const langConfig = await readLangConfig(lang);
|
||||||
|
console.log(`Checking for existing sessions`);
|
||||||
|
const existingSessions = await k8s.listUserSessions();
|
||||||
|
if (existingSessions.length > 0) {
|
||||||
|
console.log(`Killing ${existingSessions.length} existing session(s)`);
|
||||||
|
await k8s.deleteUserSessions(existingSessions);
|
||||||
|
}
|
||||||
|
const sessionID = getUUID();
|
||||||
|
console.log(`Starting session with UUID ${sessionID}`);
|
||||||
|
const watcher = k8s.watchPods();
|
||||||
|
await k8s.createUserSession({
|
||||||
|
watcher,
|
||||||
|
sessionID,
|
||||||
|
langConfig,
|
||||||
|
revisions: {
|
||||||
|
agent: "20221229-002450-semantic-moccasin-albatross",
|
||||||
|
ptyify: "20221228-023645-clean-white-gorilla",
|
||||||
|
langImage: "20221227-195753-forward-harlequin-wolverine",
|
||||||
|
},
|
||||||
|
});
|
||||||
|
// let buffer = "";
|
||||||
|
// await new Promise((resolve) => {
|
||||||
|
// session.stdout.on("data", (data) => {
|
||||||
|
// buffer += data.toString();
|
||||||
|
// let idx;
|
||||||
|
// while ((idx = buffer.indexOf("\n")) !== -1) {
|
||||||
|
// const line = buffer.slice(0, idx);
|
||||||
|
// buffer = buffer.slice(idx + 1);
|
||||||
|
// if (line === "riju: container ready") {
|
||||||
|
// resolve();
|
||||||
|
// } else {
|
||||||
|
// console.error(line);
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
// });
|
||||||
|
// });
|
||||||
|
// const args = [].concat.apply(
|
||||||
|
// ["riju-pty", "-f"],
|
||||||
|
// privilegedPty(
|
||||||
|
// { uuid },
|
||||||
|
// bash(
|
||||||
|
// `env L='${lang}' LANG_CONFIG=${quote(
|
||||||
|
// JSON.stringify(langConfig)
|
||||||
|
// )} bash --rcfile <(cat <<< ${quote(sandboxScript)})`
|
||||||
|
// )
|
||||||
|
// )
|
||||||
|
// );
|
||||||
|
// const proc = spawn(args[0], args.slice(1), {
|
||||||
|
// stdio: "inherit",
|
||||||
|
// });
|
||||||
|
// try {
|
||||||
|
// await new Promise((resolve, reject) => {
|
||||||
|
// proc.on("error", reject);
|
||||||
|
// proc.on("close", resolve);
|
||||||
|
// });
|
||||||
|
// } finally {
|
||||||
|
// session.kill();
|
||||||
|
// }
|
||||||
|
}
|
||||||
|
|
||||||
|
main().catch(die);
|
|
@ -1,17 +1,18 @@
|
||||||
|
#!/usr/bin/env bash
|
||||||
# This script is sourced by Bash within 'make sandbox'.
|
# This script is sourced by Bash within 'make sandbox'.
|
||||||
|
|
||||||
if [[ -z "$L" ]]; then
|
if [[ -z "$L" ]]; then
|
||||||
echo 'environment variable unset: $L' >&2
|
echo "environment variable unset: \$L" >&2
|
||||||
exit 1
|
exit 1
|
||||||
fi
|
fi
|
||||||
|
|
||||||
if [[ -z "$LANG_CONFIG" ]]; then
|
if [[ -z "$LANG_CONFIG" ]]; then
|
||||||
echo 'environment variable unset: $LANG_CONFIG' >&2
|
echo "environment variable unset: \$LANG_CONFIG" >&2
|
||||||
exit 1
|
exit 1
|
||||||
fi
|
fi
|
||||||
|
|
||||||
function get {
|
function get {
|
||||||
jq -r ".$1" <<< "${LANG_CONFIG}"
|
jq -r ".$1" <<<"${LANG_CONFIG}"
|
||||||
}
|
}
|
||||||
|
|
||||||
function has {
|
function has {
|
||||||
|
@ -24,21 +25,21 @@ function riju-exec {
|
||||||
|
|
||||||
function daemon {
|
function daemon {
|
||||||
if has daemon; then
|
if has daemon; then
|
||||||
echo "$(get daemon)"
|
get daemon
|
||||||
riju-exec "$(get daemon)"
|
riju-exec "$(get daemon)"
|
||||||
fi
|
fi
|
||||||
}
|
}
|
||||||
|
|
||||||
function setup {
|
function setup {
|
||||||
if has setup; then
|
if has setup; then
|
||||||
echo "$(get setup)"
|
get setup
|
||||||
riju-exec "$(get setup)"
|
riju-exec "$(get setup)"
|
||||||
fi
|
fi
|
||||||
}
|
}
|
||||||
|
|
||||||
function repl {
|
function repl {
|
||||||
if has repl; then
|
if has repl; then
|
||||||
echo "$(get repl)"
|
get repl
|
||||||
riju-exec "$(get repl)"
|
riju-exec "$(get repl)"
|
||||||
fi
|
fi
|
||||||
}
|
}
|
||||||
|
@ -47,22 +48,22 @@ function main {
|
||||||
if get main | grep -q /; then
|
if get main | grep -q /; then
|
||||||
mkdir -p "$(dirname "$(get main)")"
|
mkdir -p "$(dirname "$(get main)")"
|
||||||
fi
|
fi
|
||||||
: > "$(get main)"
|
: >"$(get main)"
|
||||||
has prefix && get prefix >> "$(get main)"
|
has prefix && get prefix >>"$(get main)"
|
||||||
get template >> "$(get main)"
|
get template >>"$(get main)"
|
||||||
has suffix && get suffix >> "$(get main)"
|
has suffix && get suffix >>"$(get main)"
|
||||||
}
|
}
|
||||||
|
|
||||||
function compile {
|
function compile {
|
||||||
if has compile; then
|
if has compile; then
|
||||||
echo "$(get compile)"
|
get compile
|
||||||
riju-exec "$(get compile)"
|
riju-exec "$(get compile)"
|
||||||
fi
|
fi
|
||||||
}
|
}
|
||||||
|
|
||||||
function run-only {
|
function run-only {
|
||||||
if has run; then
|
if has run; then
|
||||||
echo "$(get run)"
|
get run
|
||||||
riju-exec "$(get run)"
|
riju-exec "$(get run)"
|
||||||
fi
|
fi
|
||||||
}
|
}
|
||||||
|
@ -73,18 +74,18 @@ function run {
|
||||||
|
|
||||||
function format {
|
function format {
|
||||||
if has format; then
|
if has format; then
|
||||||
echo "$(get format.run)"
|
get format.run
|
||||||
riju-exec "( $(get format.run) ) < $(get main)"
|
riju-exec "( $(get format.run) ) < $(get main)"
|
||||||
fi
|
fi
|
||||||
}
|
}
|
||||||
|
|
||||||
function lsp {
|
function lsp {
|
||||||
if has lsp.setup; then
|
if has lsp.setup; then
|
||||||
echo "$(get lsp.setup)"
|
get lsp.setup
|
||||||
riju-exec "$(get lsp.setup)"
|
riju-exec "$(get lsp.setup)"
|
||||||
fi
|
fi
|
||||||
if has lsp; then
|
if has lsp; then
|
||||||
echo "$(get lsp.start)"
|
get lsp.start
|
||||||
riju-exec "$(get lsp.start)"
|
riju-exec "$(get lsp.start)"
|
||||||
fi
|
fi
|
||||||
}
|
}
|
||||||
|
|
|
@ -6,11 +6,9 @@ import { readLangConfig } from "../lib/yaml.js";
|
||||||
import {
|
import {
|
||||||
bash,
|
bash,
|
||||||
getUUID,
|
getUUID,
|
||||||
privilegedExec,
|
|
||||||
privilegedPty,
|
privilegedPty,
|
||||||
privilegedSession,
|
privilegedSession,
|
||||||
quote,
|
quote,
|
||||||
run,
|
|
||||||
} from "./util.js";
|
} from "./util.js";
|
||||||
|
|
||||||
function die(msg) {
|
function die(msg) {
|
||||||
|
@ -18,10 +16,6 @@ function die(msg) {
|
||||||
process.exit(1);
|
process.exit(1);
|
||||||
}
|
}
|
||||||
|
|
||||||
function log(msg) {
|
|
||||||
console.log(msg);
|
|
||||||
}
|
|
||||||
|
|
||||||
async function main() {
|
async function main() {
|
||||||
const sandboxScript = await fs.readFile("backend/sandbox.bash", "utf-8");
|
const sandboxScript = await fs.readFile("backend/sandbox.bash", "utf-8");
|
||||||
const lang = process.env.L;
|
const lang = process.env.L;
|
||||||
|
|
|
@ -6,6 +6,7 @@ import cors from "cors"
|
||||||
import express from "express";
|
import express from "express";
|
||||||
import ws from "express-ws";
|
import ws from "express-ws";
|
||||||
import _ from "lodash";
|
import _ from "lodash";
|
||||||
|
import * as promClient from "prom-client";
|
||||||
|
|
||||||
import * as api from "./api.js";
|
import * as api from "./api.js";
|
||||||
import { aliases, langsPromise } from "./langs.js";
|
import { aliases, langsPromise } from "./langs.js";
|
||||||
|
@ -15,12 +16,21 @@ import { log, privilegedTeardown } from "./util.js";
|
||||||
const host = process.env.HOST || "localhost";
|
const host = process.env.HOST || "localhost";
|
||||||
const port = parseInt(process.env.PORT || "") || 6119;
|
const port = parseInt(process.env.PORT || "") || 6119;
|
||||||
const tlsPort = parseInt(process.env.TLS_PORT || "") || 6120;
|
const tlsPort = parseInt(process.env.TLS_PORT || "") || 6120;
|
||||||
|
const metricsPort = parseInt(process.env.METRICS_PORT || "") || 6121;
|
||||||
const useTLS = process.env.TLS ? true : false;
|
const useTLS = process.env.TLS ? true : false;
|
||||||
const analyticsTag = (process.env.ANALYTICS_TAG || "").replace(
|
const analyticsTag = (process.env.ANALYTICS_TAG || "").replace(
|
||||||
/^'(.+)'$/,
|
/^'(.+)'$/,
|
||||||
"$1"
|
"$1"
|
||||||
);
|
);
|
||||||
|
|
||||||
|
promClient.collectDefaultMetrics();
|
||||||
|
|
||||||
|
const metricsApp = express();
|
||||||
|
metricsApp.get("/metrics", async (_, res) => {
|
||||||
|
res.contentType("text/plain; version=0.0.4");
|
||||||
|
res.send(await promClient.register.metrics());
|
||||||
|
});
|
||||||
|
|
||||||
const langs = await langsPromise;
|
const langs = await langsPromise;
|
||||||
const app = express();
|
const app = express();
|
||||||
|
|
||||||
|
@ -142,3 +152,7 @@ if (useTLS) {
|
||||||
console.log(`Listening on http://${host}:${port}`)
|
console.log(`Listening on http://${host}:${port}`)
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
metricsApp.listen(metricsPort, host, () =>
|
||||||
|
console.log(`Listening on http://${host}:${metricsPort}/metrics`)
|
||||||
|
);
|
||||||
|
|
|
@ -10,7 +10,7 @@ import { getTestHash } from "../lib/hash-test.js";
|
||||||
import * as api from "./api.js";
|
import * as api from "./api.js";
|
||||||
import { langsPromise } from "./langs.js";
|
import { langsPromise } from "./langs.js";
|
||||||
import { shutdown } from "./shutdown.js";
|
import { shutdown } from "./shutdown.js";
|
||||||
import { getUUID, run } from "./util.js";
|
import { run } from "./util.js";
|
||||||
|
|
||||||
let langs = {};
|
let langs = {};
|
||||||
|
|
||||||
|
@ -525,7 +525,7 @@ const testTypes = {
|
||||||
ensure: {
|
ensure: {
|
||||||
pred: ({ ensure }) => (ensure ? true : false),
|
pred: ({ ensure }) => (ensure ? true : false),
|
||||||
},
|
},
|
||||||
run: { pred: (config) => true },
|
run: { pred: (_config) => true },
|
||||||
repl: {
|
repl: {
|
||||||
pred: ({ repl }) => (repl ? true : false),
|
pred: ({ repl }) => (repl ? true : false),
|
||||||
},
|
},
|
||||||
|
|
|
@ -1,5 +1,4 @@
|
||||||
import { spawn } from "child_process";
|
import { spawn } from "child_process";
|
||||||
import os from "os";
|
|
||||||
import process from "process";
|
import process from "process";
|
||||||
|
|
||||||
import * as Sentry from "@sentry/node";
|
import * as Sentry from "@sentry/node";
|
||||||
|
@ -89,6 +88,14 @@ export async function run(args, log, options) {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function privilegedList() {
|
||||||
|
return [rijuSystemPrivileged, "list"];
|
||||||
|
}
|
||||||
|
|
||||||
|
export function privilegedPull({ repo, tag }) {
|
||||||
|
return [rijuSystemPrivileged, "pull", repo, tag];
|
||||||
|
}
|
||||||
|
|
||||||
export function privilegedSession({ uuid, lang }) {
|
export function privilegedSession({ uuid, lang }) {
|
||||||
const cmdline = [rijuSystemPrivileged, "session", uuid, lang];
|
const cmdline = [rijuSystemPrivileged, "session", uuid, lang];
|
||||||
if (imageHashes[lang]) {
|
if (imageHashes[lang]) {
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
module github.com/raxod502/riju/cli
|
module github.com/radian-software/riju/cli
|
||||||
|
|
||||||
go 1.16
|
go 1.16
|
||||||
|
|
||||||
|
|
|
@ -13,7 +13,7 @@ hesitate to open an issue!
|
||||||
Clone locally:
|
Clone locally:
|
||||||
|
|
||||||
```
|
```
|
||||||
$ git clone https://github.com/raxod502/riju.git
|
$ git clone https://github.com/radian-software/riju.git
|
||||||
$ cd riju
|
$ cd riju
|
||||||
```
|
```
|
||||||
|
|
||||||
|
|
|
@ -111,8 +111,8 @@ API. Generate one randomly with `pwgen -s 30 1`.
|
||||||
## Build web AMI
|
## Build web AMI
|
||||||
|
|
||||||
You'll want to run `set -a; . .env` to load in the new variables from
|
You'll want to run `set -a; . .env` to load in the new variables from
|
||||||
`.env`. Now run `make packer-web`. This will take up to 10 minutes to
|
`.env`. Now run `make packer`. This will take up to 10 minutes to
|
||||||
build a timestamped AMI with a name like `riju-web-20210711223158`.
|
build a timestamped AMI with a name like `riju-20210711223158`.
|
||||||
|
|
||||||
## Create local configuration (part 2 of 3)
|
## Create local configuration (part 2 of 3)
|
||||||
|
|
||||||
|
@ -120,15 +120,16 @@ Add to `.env` the following contents:
|
||||||
|
|
||||||
```
|
```
|
||||||
# Terraform
|
# Terraform
|
||||||
AMI_NAME=riju-web-20210711223158
|
AMI_NAME=riju-20210711223158
|
||||||
AWS_REGION=us-west-1
|
AWS_REGION=us-west-1
|
||||||
DOMAIN=your.domain
|
DOMAIN=your.domain
|
||||||
S3_BUCKET=yourname-riju
|
S3_BUCKET=yourname-riju
|
||||||
|
S3_CONFIG_PATH=config.json
|
||||||
```
|
```
|
||||||
|
|
||||||
### AMI\_NAME
|
### AMI\_NAME
|
||||||
|
|
||||||
This is the AMI name from `make packer-web`.
|
This is the AMI name from `make packer`.
|
||||||
|
|
||||||
### AWS\_REGION
|
### AWS\_REGION
|
||||||
|
|
||||||
|
@ -168,7 +169,7 @@ infrastructure.
|
||||||
follow these steps:*
|
follow these steps:*
|
||||||
|
|
||||||
1. Update `.env` and make sure it is sourced (`set -a; . .env`).
|
1. Update `.env` and make sure it is sourced (`set -a; . .env`).
|
||||||
2. Run `make packer-web` and get the name of the new AMI.
|
2. Run `make packer` and get the name of the new AMI.
|
||||||
3. Update it in `.env` under `AMI_NAME` and make sure the update is
|
3. Update it in `.env` under `AMI_NAME` and make sure the update is
|
||||||
sourced (`set -a; . .env`).
|
sourced (`set -a; . .env`).
|
||||||
4. Run `terraform apply`.
|
4. Run `terraform apply`.
|
||||||
|
@ -231,7 +232,7 @@ from the load balancer).
|
||||||
to upload the finished build artifacts to ECR, which amount to about
|
to upload the finished build artifacts to ECR, which amount to about
|
||||||
40 GB of data transfer. If you don't have a symmetric Internet plan at
|
40 GB of data transfer. If you don't have a symmetric Internet plan at
|
||||||
home, you may need to do this on an EC2 instance instead. You can
|
home, you may need to do this on an EC2 instance instead. You can
|
||||||
provision one manually with at least 256 GB of disk space, install
|
provision one manually with at least 128 GB of disk space, install
|
||||||
Docker, clone down Riju, copy over your `.env` file, and proceed as if
|
Docker, clone down Riju, copy over your `.env` file, and proceed as if
|
||||||
you were running locally.)*
|
you were running locally.)*
|
||||||
|
|
||||||
|
|
|
@ -49,9 +49,9 @@ requirements:
|
||||||
because it only runs on macOS, and [Docker](https://www.docker.com/)
|
because it only runs on macOS, and [Docker](https://www.docker.com/)
|
||||||
is out because it can't be run inside Docker (without the
|
is out because it can't be run inside Docker (without the
|
||||||
`--privileged` flag, which has unacceptable security drawbacks; see
|
`--privileged` flag, which has unacceptable security drawbacks; see
|
||||||
[#29](https://github.com/raxod502/riju/issues/29)). Note, however,
|
[#29](https://github.com/radian-software/riju/issues/29)). Note,
|
||||||
that many Windows-based languages can be used successfully via
|
however, that many Windows-based languages can be used successfully
|
||||||
[Mono](https://www.mono-project.com/) or
|
via [Mono](https://www.mono-project.com/) or
|
||||||
[Wine](https://www.winehq.org/), such as
|
[Wine](https://www.winehq.org/), such as
|
||||||
[Cmd](https://en.wikipedia.org/wiki/Cmd.exe),
|
[Cmd](https://en.wikipedia.org/wiki/Cmd.exe),
|
||||||
[C#](https://en.wikipedia.org/wiki/C_Sharp_(programming_language)),
|
[C#](https://en.wikipedia.org/wiki/C_Sharp_(programming_language)),
|
||||||
|
@ -60,7 +60,8 @@ requirements:
|
||||||
Here are some explicit *non-requirements*:
|
Here are some explicit *non-requirements*:
|
||||||
|
|
||||||
* *Language must be well-known.* Nope, I'll be happy to add your pet
|
* *Language must be well-known.* Nope, I'll be happy to add your pet
|
||||||
project; after all, [Kalyn](https://github.com/raxod502/kalyn) and
|
project; after all,
|
||||||
|
[Kalyn](https://github.com/radian-software/kalyn) and
|
||||||
[Ink](https://github.com/thesephist/ink) are already supported.
|
[Ink](https://github.com/thesephist/ink) are already supported.
|
||||||
* *Language must be useful.* I have no objection to adding everything
|
* *Language must be useful.* I have no objection to adding everything
|
||||||
on the esolangs wiki, if there are interpreters/compilers available.
|
on the esolangs wiki, if there are interpreters/compilers available.
|
||||||
|
|
|
@ -2,7 +2,12 @@
|
||||||
|
|
||||||
set -euxo pipefail
|
set -euxo pipefail
|
||||||
|
|
||||||
pushd /tmp
|
latest_release() {
|
||||||
|
curl -sSL "https://api.github.com/repos/$1/releases/latest" | jq -r .tag_name
|
||||||
|
}
|
||||||
|
|
||||||
|
mkdir /tmp/riju-work
|
||||||
|
pushd /tmp/riju-work
|
||||||
|
|
||||||
export DEBIAN_FRONTEND=noninteractive
|
export DEBIAN_FRONTEND=noninteractive
|
||||||
|
|
||||||
|
@ -39,6 +44,7 @@ dctrl-tools
|
||||||
docker-ce-cli
|
docker-ce-cli
|
||||||
file
|
file
|
||||||
g++
|
g++
|
||||||
|
gettext
|
||||||
git
|
git
|
||||||
golang
|
golang
|
||||||
htop
|
htop
|
||||||
|
@ -57,8 +63,9 @@ skopeo
|
||||||
ssh
|
ssh
|
||||||
strace
|
strace
|
||||||
sudo
|
sudo
|
||||||
tmux
|
|
||||||
terraform
|
terraform
|
||||||
|
tmux
|
||||||
|
tree
|
||||||
unzip
|
unzip
|
||||||
uuid-runtime
|
uuid-runtime
|
||||||
vim
|
vim
|
||||||
|
@ -77,7 +84,11 @@ npm install -g prettier
|
||||||
wget -nv https://awscli.amazonaws.com/awscli-exe-linux-x86_64.zip -O awscli.zip
|
wget -nv https://awscli.amazonaws.com/awscli-exe-linux-x86_64.zip -O awscli.zip
|
||||||
unzip -q awscli.zip
|
unzip -q awscli.zip
|
||||||
./aws/install
|
./aws/install
|
||||||
rm -rf aws awscli.zip
|
|
||||||
|
ver="$(latest_release grafana/cortex-tools | sed 's/^v//')"
|
||||||
|
wget -nv "https://github.com/grafana/cortex-tools/releases/download/v${ver}/cortextool_${ver}_linux_amd64.tar.gz" -O cortextool.tar.gz
|
||||||
|
tar -xf cortextool.tar.gz
|
||||||
|
cp cortextool /usr/local/bin/
|
||||||
|
|
||||||
rm -rf /var/lib/apt/lists/*
|
rm -rf /var/lib/apt/lists/*
|
||||||
|
|
||||||
|
@ -86,5 +97,6 @@ tee /etc/sudoers.d/90-riju >/dev/null <<"EOF"
|
||||||
EOF
|
EOF
|
||||||
|
|
||||||
popd
|
popd
|
||||||
|
rm -rf /tmp/riju-work
|
||||||
|
|
||||||
rm "$0"
|
rm "$0"
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
FROM riju:ubuntu AS build
|
# EOL: April 2027
|
||||||
|
FROM ubuntu:22.04 AS build
|
||||||
|
|
||||||
COPY docker/app/install-build.bash /tmp/
|
COPY docker/app/install-build.bash /tmp/
|
||||||
RUN /tmp/install-build.bash
|
RUN /tmp/install-build.bash
|
||||||
|
|
|
@ -15,7 +15,7 @@ curl -sSL https://dl.yarnpkg.com/debian/pubkey.gpg | apt-key add -
|
||||||
ubuntu_ver="$(lsb_release -rs)"
|
ubuntu_ver="$(lsb_release -rs)"
|
||||||
ubuntu_name="$(lsb_release -cs)"
|
ubuntu_name="$(lsb_release -cs)"
|
||||||
|
|
||||||
node_repo="$(curl -sS https://deb.nodesource.com/setup_current.x | grep NODEREPO= | grep -Eo 'node_[0-9]+\.x' | head -n1)"
|
node_repo="$(curl -sS https://deb.nodesource.com/setup_16.x | grep NODEREPO= | grep -Eo 'node_[0-9]+\.x' | head -n1)"
|
||||||
|
|
||||||
tee -a /etc/apt/sources.list.d/custom.list >/dev/null <<EOF
|
tee -a /etc/apt/sources.list.d/custom.list >/dev/null <<EOF
|
||||||
deb https://deb.nodesource.com/${node_repo} ${ubuntu_name} main
|
deb https://deb.nodesource.com/${node_repo} ${ubuntu_name} main
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
FROM riju:ubuntu
|
# EOL: April 2027
|
||||||
|
FROM ubuntu:22.04
|
||||||
|
|
||||||
COPY docker/base/install.bash /tmp/
|
COPY docker/base/install.bash /tmp/
|
||||||
RUN /tmp/install.bash
|
RUN /tmp/install.bash
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
FROM ubuntu:rolling
|
FROM ubuntu:21.04
|
||||||
|
|
||||||
COPY docker/ci/install.bash /tmp/
|
COPY docker/ci/install.bash /tmp/
|
||||||
RUN /tmp/install.bash
|
RUN /tmp/install.bash
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
FROM riju:ubuntu
|
# EOL: April 2027
|
||||||
|
FROM ubuntu:22.04
|
||||||
|
|
||||||
COPY docker/packaging/install.bash /tmp/
|
COPY docker/packaging/install.bash /tmp/
|
||||||
RUN /tmp/install.bash
|
RUN /tmp/install.bash
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
FROM riju:ubuntu
|
# EOL: April 2027
|
||||||
|
FROM ubuntu:22.04
|
||||||
|
|
||||||
COPY docker/runtime/install.bash /tmp/
|
COPY docker/runtime/install.bash /tmp/
|
||||||
RUN /tmp/install.bash
|
RUN /tmp/install.bash
|
||||||
|
@ -10,4 +11,5 @@ WORKDIR /src
|
||||||
CMD ["bash"]
|
CMD ["bash"]
|
||||||
EXPOSE 6119
|
EXPOSE 6119
|
||||||
EXPOSE 6120
|
EXPOSE 6120
|
||||||
|
EXPOSE 6121
|
||||||
ENV HOST=0.0.0.0
|
ENV HOST=0.0.0.0
|
||||||
|
|
|
@ -2,8 +2,8 @@
|
||||||
|
|
||||||
set -euxo pipefail
|
set -euxo pipefail
|
||||||
|
|
||||||
latest_release() {
|
latest_watchexec_release() {
|
||||||
curl -sSL "https://api.github.com/repos/$1/releases/latest" | jq -r .tag_name
|
curl -sSL "https://api.github.com/repos/$1/releases" | jq -c -r '[.[] | select(.tag_name | test("^cli-v"))] | first | .tag_name'
|
||||||
}
|
}
|
||||||
|
|
||||||
mkdir /tmp/riju-work
|
mkdir /tmp/riju-work
|
||||||
|
@ -23,7 +23,7 @@ curl -fsSL https://download.docker.com/linux/ubuntu/gpg | apt-key add -
|
||||||
|
|
||||||
ubuntu_name="$(lsb_release -cs)"
|
ubuntu_name="$(lsb_release -cs)"
|
||||||
|
|
||||||
node_repo="$(curl -sS https://deb.nodesource.com/setup_current.x | grep NODEREPO= | grep -Eo 'node_[0-9]+\.x' | head -n1)"
|
node_repo="$(curl -sS https://deb.nodesource.com/setup_16.x | grep NODEREPO= | grep -Eo 'node_[0-9]+\.x' | head -n1)"
|
||||||
|
|
||||||
tee -a /etc/apt/sources.list.d/custom.list >/dev/null <<EOF
|
tee -a /etc/apt/sources.list.d/custom.list >/dev/null <<EOF
|
||||||
deb [arch=amd64] https://deb.nodesource.com/${node_repo} ${ubuntu_name} main
|
deb [arch=amd64] https://deb.nodesource.com/${node_repo} ${ubuntu_name} main
|
||||||
|
@ -72,7 +72,7 @@ apt-get install -y $(sed 's/#.*//' <<< "${packages}")
|
||||||
|
|
||||||
pip3 install poetry
|
pip3 install poetry
|
||||||
|
|
||||||
ver="$(latest_release watchexec/watchexec | sed 's/^cli-v//')"
|
ver="$(latest_watchexec_release watchexec/watchexec | sed 's/^cli-v//')"
|
||||||
wget "https://github.com/watchexec/watchexec/releases/download/cli-v${ver}/watchexec-${ver}-x86_64-unknown-linux-gnu.deb"
|
wget "https://github.com/watchexec/watchexec/releases/download/cli-v${ver}/watchexec-${ver}-x86_64-unknown-linux-gnu.deb"
|
||||||
apt-get install -y ./watchexec-*.deb
|
apt-get install -y ./watchexec-*.deb
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,33 @@
|
||||||
|
#!/usr/bin/env bash
|
||||||
|
|
||||||
|
set -euo pipefail
|
||||||
|
|
||||||
|
cd "$(dirname "$0")"
|
||||||
|
|
||||||
|
registry_password="$(pwgen -s 20 1)"
|
||||||
|
proxy_password="$(pwgen -s 20 1)"
|
||||||
|
|
||||||
|
cat <<EOF
|
||||||
|
networking:
|
||||||
|
domain: riju.example.com # FIXME
|
||||||
|
ip: x.y.z.w # FIXME
|
||||||
|
|
||||||
|
contact:
|
||||||
|
letsEncryptEmail: ops@example.com # FIXME
|
||||||
|
letsEncryptProductionEnabled: false
|
||||||
|
|
||||||
|
metallb:
|
||||||
|
secretkey: "$(pwgen -s 256 1)"
|
||||||
|
|
||||||
|
registry:
|
||||||
|
password: "${registry_password}"
|
||||||
|
htpasswd: "$(htpasswd -nbB admin "${registry_password}")"
|
||||||
|
|
||||||
|
minio:
|
||||||
|
accessKey: "$(head -c16 /dev/urandom | xxd -p)"
|
||||||
|
secretKey: "$(head -c16 /dev/urandom | xxd -p)"
|
||||||
|
|
||||||
|
proxy:
|
||||||
|
password: "${proxy_password}"
|
||||||
|
htpasswd: "$(htpasswd -nbB admin "${proxy_password}")"
|
||||||
|
EOF
|
|
@ -1,61 +0,0 @@
|
||||||
Riju :: $169.46
|
|
||||||
CloudWatch :: $34.80
|
|
||||||
EC2 :: $107.01
|
|
||||||
Data Transfer :: $0.68
|
|
||||||
EBS Snapshot :: $5.45
|
|
||||||
EBS Volume :: $46.40
|
|
||||||
EBS Volume :: $46.40
|
|
||||||
gp2 :: $11.61
|
|
||||||
gp3 :: $34.78
|
|
||||||
Instance :: $54.48
|
|
||||||
t2.small :: $0.04
|
|
||||||
t3 :: $0.08
|
|
||||||
t3.2xlarge :: $29.80
|
|
||||||
t3.medium :: $14.77
|
|
||||||
t3.small :: $9.78
|
|
||||||
ECR :: $7.31
|
|
||||||
Data Transfer :: $3.29
|
|
||||||
Storage :: $4.02
|
|
||||||
ELB :: $20.05
|
|
||||||
Data Transfer :: $0.31
|
|
||||||
LCUs :: $0.06
|
|
||||||
Load Balancer :: $19.68
|
|
||||||
S3 :: $0.29
|
|
||||||
|
|
||||||
COMMENTARY: This month was a disaster because AWS makes it really hard
|
|
||||||
to understand what exactly is going to run up your bill.
|
|
||||||
|
|
||||||
The most egregious thing here is CloudWatch. It turns out that if you
|
|
||||||
follow the official documentation for how to set up a CloudWatch alarm
|
|
||||||
on disk space for your EC2 instance, the default configuration has SSM
|
|
||||||
Agent creating a metric for *every* filesystem mounted on your
|
|
||||||
instance, which is actually one or more per Docker container, so I
|
|
||||||
actually had like multiple tens of thousands of metrics being shipped
|
|
||||||
to CloudWatch, which is expensive. I fixed this for August, bringing
|
|
||||||
CloudWatch costs to be effectively zero.
|
|
||||||
|
|
||||||
We have some charges for a t3.medium, this is before I scaled the
|
|
||||||
server down to t3.small. The charges for that instance are also higher
|
|
||||||
than you'd expect because I was originally running two of them before
|
|
||||||
scaling it down for a singleton because I realized I was out of my
|
|
||||||
depth.
|
|
||||||
|
|
||||||
We had a couple gp2 volumes (more expensive) before I migrated
|
|
||||||
everything to gp3. EBS costs are generally quite high here because not
|
|
||||||
only did I previously have two instances serving traffic, but I also
|
|
||||||
had a dev server. Each of those three instances had to have the full
|
|
||||||
256 GB data volume to store language images, which was ridiculously
|
|
||||||
expensive. I'm planning on keeping Riju as a singleton for a while
|
|
||||||
because of this issue, and relying on vertical scaling until that
|
|
||||||
becomes no longer feasible. The persistent dev server will be replaced
|
|
||||||
by a transient CI instance that can be spun up to do large rebuild
|
|
||||||
operations, mitigating EBS costs.
|
|
||||||
|
|
||||||
t3.2xlarge is the dev server, this is mostly just tough luck since I
|
|
||||||
did need to spend a lot of time building and rebuilding language
|
|
||||||
images and those hours add up. Hopefully that won't be as much of an
|
|
||||||
issue going forward now that the infrastructure is more stable and we
|
|
||||||
can hopefully get away without a dev server in general. But
|
|
||||||
fundamentally you can't do builds on your local laptop without a
|
|
||||||
symmetric Internet plan because you need to upload like 100 GB for a
|
|
||||||
full rebuild.
|
|
|
@ -1,25 +0,0 @@
|
||||||
Riju :: $58.75
|
|
||||||
EC2 :: $32.26
|
|
||||||
Data Transfer :: $0.04
|
|
||||||
EBS Snapshot :: $1.67
|
|
||||||
EBS Volume :: $18.46
|
|
||||||
EBS Volume :: $18.46
|
|
||||||
gp2 :: $0.69
|
|
||||||
gp3 :: $17.77
|
|
||||||
Instance :: $12.09
|
|
||||||
t3.small :: $12.09
|
|
||||||
ECR :: $6.42
|
|
||||||
Data Transfer :: $1.38
|
|
||||||
Storage :: $5.05
|
|
||||||
ELB :: $19.93
|
|
||||||
Data Transfer :: $0.18
|
|
||||||
LCUs :: $0.06
|
|
||||||
Load Balancer :: $19.68
|
|
||||||
S3 :: $0.13
|
|
||||||
|
|
||||||
COMMENTARY: I think we could save on ELB costs by migrating to Lambda;
|
|
||||||
see https://github.com/raxod502/riju/issues/93 for that. Otherwise,
|
|
||||||
the main important thing to note about this month is that I had part
|
|
||||||
of the infrastructure spun down for a significant part of it, as per
|
|
||||||
https://riju.statuspage.io/ (Aug 1 through Aug 16). So costs are
|
|
||||||
liable to increase next month now that we are in normal operation.
|
|
|
@ -1,27 +0,0 @@
|
||||||
Riju :: $81.55
|
|
||||||
EC2 :: $57.02
|
|
||||||
Data Transfer :: $0.02
|
|
||||||
EBS Snapshot :: $1.97
|
|
||||||
EBS Volume :: $26.82
|
|
||||||
EBS Volume :: $26.82
|
|
||||||
gp2 :: $1.01
|
|
||||||
gp3 :: $25.81
|
|
||||||
Instance :: $28.21
|
|
||||||
t3.medium :: $19.01
|
|
||||||
t3.small :: $9.21
|
|
||||||
ECR :: $5.09
|
|
||||||
Storage :: $5.09
|
|
||||||
ELB :: $19.32
|
|
||||||
Data Transfer :: $0.22
|
|
||||||
LCUs :: $0.06
|
|
||||||
Load Balancer :: $19.04
|
|
||||||
S3 :: $0.12
|
|
||||||
|
|
||||||
COMMENTARY: We're starting to look pretty stable from month to month.
|
|
||||||
Naturally the costs are higher because we were operating the
|
|
||||||
infrastructure for the entire month this time, instead of being down
|
|
||||||
for half of it, but I think this cost is about what we should expect
|
|
||||||
to see going forward until changes are made.
|
|
||||||
|
|
||||||
I did realize, by the way, that we can't use Lambda to replace the
|
|
||||||
ELB, because that wouldn't support websockets. Oh well.
|
|
|
@ -1,18 +0,0 @@
|
||||||
Riju :: $106.77
|
|
||||||
EC2 :: $81.38
|
|
||||||
Data Transfer :: $0.03
|
|
||||||
EBS Snapshot :: $2.36
|
|
||||||
EBS Volume :: $28.57
|
|
||||||
EBS Volume :: $28.57
|
|
||||||
gp2 :: $1.07
|
|
||||||
gp3 :: $27.49
|
|
||||||
Instance :: $50.43
|
|
||||||
t3.large :: $23.05
|
|
||||||
t3.medium :: $27.38
|
|
||||||
ECR :: $5.14
|
|
||||||
Storage :: $5.14
|
|
||||||
ELB :: $20.14
|
|
||||||
Data Transfer :: $0.38
|
|
||||||
LCUs :: $0.07
|
|
||||||
Load Balancer :: $19.68
|
|
||||||
S3 :: $0.11
|
|
|
@ -1,14 +1,4 @@
|
||||||
# Riju financials
|
# Riju financials
|
||||||
|
|
||||||
This directory has a Python script that can download and analyze
|
This data has all moved to
|
||||||
billing data from AWS to determine how much Riju actually costs. This
|
[radian-software/financials](https://github.com/radian-software/financials).
|
||||||
information is then made publicly available in per-month
|
|
||||||
subdirectories here; for some months with unusual charges I've added
|
|
||||||
commentary to explain what was going on.
|
|
||||||
|
|
||||||
This information is then imported into [Riju's master budgeting
|
|
||||||
spreadsheet](https://docs.google.com/spreadsheets/d/15Us9KLXaJ6B1lNhrM6GV6JmmeKqNc8NNeTnaWiAhozw/edit?usp=sharing)
|
|
||||||
which compares spending to donations in order to determine whether we
|
|
||||||
are making a profit (we are not...). Once we start making a profit we
|
|
||||||
can start donating to the EFF as promised, or scale up Riju's
|
|
||||||
infrastructure to support more users for free.
|
|
||||||
|
|
|
@ -1,310 +0,0 @@
|
||||||
#!/usr/bin/env python3
|
|
||||||
|
|
||||||
import argparse
|
|
||||||
import collections
|
|
||||||
import csv
|
|
||||||
import decimal
|
|
||||||
import gzip
|
|
||||||
import io
|
|
||||||
import json
|
|
||||||
import logging
|
|
||||||
import os
|
|
||||||
import pathlib
|
|
||||||
import re
|
|
||||||
import sys
|
|
||||||
from urllib.parse import urlparse
|
|
||||||
|
|
||||||
import boto3
|
|
||||||
|
|
||||||
logging.basicConfig(level=logging.INFO)
|
|
||||||
|
|
||||||
ROOT = pathlib.Path(__file__).parent
|
|
||||||
|
|
||||||
|
|
||||||
def die(msg):
|
|
||||||
raise AssertionError(msg)
|
|
||||||
|
|
||||||
|
|
||||||
def get_csv(year, month, force_download=False):
|
|
||||||
target_dir = ROOT / f"{year}-{month:02d}"
|
|
||||||
logging.info(f"Using base directory {target_dir}")
|
|
||||||
target_dir.mkdir(exist_ok=True)
|
|
||||||
latest_csv = target_dir / "latest.csv"
|
|
||||||
if force_download or not latest_csv.exists():
|
|
||||||
try:
|
|
||||||
latest_csv.unlink()
|
|
||||||
except FileNotFoundError:
|
|
||||||
pass
|
|
||||||
s3 = boto3.client("s3")
|
|
||||||
o = urlparse(os.environ["BILLING_REPORTS_URL"], allow_fragments=False)
|
|
||||||
assert o.scheme == "s3"
|
|
||||||
bucket = o.netloc
|
|
||||||
base_prefix = o.path.strip("/") + "/"
|
|
||||||
report_name = base_prefix.rstrip("/").split("/")[-1]
|
|
||||||
logging.info(f"List s3://{bucket}/{base_prefix}")
|
|
||||||
month_prefixes = [
|
|
||||||
elt["Prefix"]
|
|
||||||
for elt in s3.list_objects_v2(
|
|
||||||
Bucket=bucket, Prefix=f"{base_prefix}", Delimiter="/"
|
|
||||||
)["CommonPrefixes"]
|
|
||||||
]
|
|
||||||
if not month_prefixes:
|
|
||||||
die("no report prefixes found")
|
|
||||||
expected_month_prefix = f"{base_prefix}{year}{month:02d}"
|
|
||||||
matching_month_prefixes = [
|
|
||||||
p for p in month_prefixes if p.startswith(expected_month_prefix)
|
|
||||||
]
|
|
||||||
if not matching_month_prefixes:
|
|
||||||
die(f"no report prefix for the specified month ({expected_month_prefix})")
|
|
||||||
if len(matching_month_prefixes) > 1:
|
|
||||||
die(f"multiple matching report prefixes: {repr(matching_month_prefixes)}")
|
|
||||||
(month_prefix,) = matching_month_prefixes
|
|
||||||
stream = io.BytesIO()
|
|
||||||
manifest_path = f"{month_prefix}{report_name}-Manifest.json"
|
|
||||||
logging.info(f"Download s3://{bucket}/{manifest_path} in-memory")
|
|
||||||
s3.download_fileobj(bucket, manifest_path, stream)
|
|
||||||
manifest = json.loads(stream.getvalue())
|
|
||||||
(report_path,) = manifest["reportKeys"]
|
|
||||||
if not report_path.endswith(".csv.gz"):
|
|
||||||
die(f"unexpected report extension in {report_path}")
|
|
||||||
logging.info(f"Get metadata for s3://{bucket}/{report_path}")
|
|
||||||
basename = s3.head_object(Bucket=bucket, Key=report_path)[
|
|
||||||
"LastModified"
|
|
||||||
].strftime("%Y-%m-%d")
|
|
||||||
logging.info(
|
|
||||||
f"Download s3://{bucket}/{report_path} to {target_dir.relative_to(ROOT)}/{basename}.csv.gz"
|
|
||||||
)
|
|
||||||
s3.download_file(bucket, report_path, f"{target_dir}/{basename}.csv.gz")
|
|
||||||
logging.info(f"Decompress {basename}.csv.gz")
|
|
||||||
with gzip.open(f"{target_dir}/{basename}.csv.gz") as f_read:
|
|
||||||
with open(f"{target_dir}/{basename}.csv", "wb") as f_write:
|
|
||||||
while chunk := f_read.read(1024):
|
|
||||||
f_write.write(chunk)
|
|
||||||
latest_csv.symlink_to(f"{basename}.csv")
|
|
||||||
return latest_csv
|
|
||||||
|
|
||||||
|
|
||||||
def read_csv(csv_path):
|
|
||||||
rows = []
|
|
||||||
with open(csv_path) as f:
|
|
||||||
reader = csv.reader(f)
|
|
||||||
header = next(reader)
|
|
||||||
for row in reader:
|
|
||||||
rows.append(dict((key, val) for (key, val) in zip(header, row) if val))
|
|
||||||
return rows
|
|
||||||
|
|
||||||
|
|
||||||
def get_tax_key(item):
|
|
||||||
service = item["lineItem/ProductCode"]
|
|
||||||
usage_type = item["lineItem/UsageType"]
|
|
||||||
if "DataTransfer" in usage_type:
|
|
||||||
service = "AWSDataTransfer"
|
|
||||||
return (service, usage_type)
|
|
||||||
|
|
||||||
|
|
||||||
def embed_taxes(items):
|
|
||||||
tax_items = collections.defaultdict(list)
|
|
||||||
usage_items = collections.defaultdict(list)
|
|
||||||
for item in items:
|
|
||||||
item_type = item["lineItem/LineItemType"]
|
|
||||||
if item_type == "Tax":
|
|
||||||
tax_items[get_tax_key(item)].append(item)
|
|
||||||
elif item_type == "Usage":
|
|
||||||
usage_items[get_tax_key(item)].append(item)
|
|
||||||
else:
|
|
||||||
die(f"unexpected line item type {repr(item_type)}")
|
|
||||||
for key in tax_items:
|
|
||||||
if key not in usage_items:
|
|
||||||
die(f"tax for {repr(key)} but no usage for that key")
|
|
||||||
tax_cost = sum(item["lineItem/UnblendedCost"] for item in tax_items[key])
|
|
||||||
usage_cost = sum(item["lineItem/UnblendedCost"] for item in usage_items[key])
|
|
||||||
tax_multiplier = (tax_cost + usage_cost) / usage_cost
|
|
||||||
for item in usage_items[key]:
|
|
||||||
item["lineItem/UnblendedCost"] *= tax_multiplier
|
|
||||||
return [item for group in usage_items.values() for item in group]
|
|
||||||
|
|
||||||
|
|
||||||
def classify_line_item(item, billing_month=None, full=False):
|
|
||||||
service = item["lineItem/ProductCode"]
|
|
||||||
usage_type = item["lineItem/UsageType"]
|
|
||||||
operation = item.get("lineItem/Operation")
|
|
||||||
resource = item.get("lineItem/ResourceId")
|
|
||||||
project = item.get("resourceTags/user:BillingCategory")
|
|
||||||
# In 2021-07, the first month that I was using AWS resources for
|
|
||||||
# Riju in a nontrivial capacity, I had subpar billing
|
|
||||||
# observability, so a lot of the resources aren't tagged
|
|
||||||
# correctly. So for that month specifically, I'm hacking in a
|
|
||||||
# couple of heuristics to tag the resources after the fact based
|
|
||||||
# on what I know about my usage of AWS.
|
|
||||||
if billing_month == "2021-07":
|
|
||||||
if resource and "riju" in resource.lower():
|
|
||||||
project = "Riju"
|
|
||||||
elif resource and "shallan" in resource.lower():
|
|
||||||
project = "Shallan"
|
|
||||||
elif resource and "veidt" in resource.lower():
|
|
||||||
project = "Veidt"
|
|
||||||
elif service == "AmazonCloudWatch":
|
|
||||||
project = "Riju"
|
|
||||||
elif (
|
|
||||||
service == "AmazonEC2"
|
|
||||||
and resource != "i-077884b74aba86bac"
|
|
||||||
and "ElasticIP:IdleAddress" not in usage_type
|
|
||||||
and "EBS:SnapshotUsage" not in usage_type
|
|
||||||
):
|
|
||||||
project = "Riju"
|
|
||||||
# AWS does not let you put tags on a public ECR repository,
|
|
||||||
# yippee.
|
|
||||||
if service == "AmazonECRPublic" and resource.endswith("repository/riju"):
|
|
||||||
project = "Riju"
|
|
||||||
category = [
|
|
||||||
"Uncategorized",
|
|
||||||
service,
|
|
||||||
usage_type,
|
|
||||||
operation or "(no operation)",
|
|
||||||
resource or "(no resource)",
|
|
||||||
]
|
|
||||||
if not full:
|
|
||||||
if service == "AmazonS3":
|
|
||||||
category = ["S3"]
|
|
||||||
elif service == "AmazonSNS":
|
|
||||||
category = ["SNS"]
|
|
||||||
elif service in ("AmazonECR", "AmazonECRPublic"):
|
|
||||||
category = ["ECR"]
|
|
||||||
if "DataTransfer" in usage_type:
|
|
||||||
category.append("Data Transfer")
|
|
||||||
elif "TimedStorage" in usage_type:
|
|
||||||
category.append("Storage")
|
|
||||||
else:
|
|
||||||
category.extend(
|
|
||||||
[
|
|
||||||
"Uncategorized",
|
|
||||||
usage_type,
|
|
||||||
operation or "(no operation)",
|
|
||||||
resource or "(no resource)",
|
|
||||||
]
|
|
||||||
)
|
|
||||||
elif service == "AmazonEC2":
|
|
||||||
category = ["EC2"]
|
|
||||||
if "ElasticIP:IdleAddress" in usage_type:
|
|
||||||
category.append("EIP")
|
|
||||||
# Apparently tags on EIPs are ignored for billing
|
|
||||||
# purposes, so we just have to know what we were using
|
|
||||||
# them for. (Leaving them uncategorized for 2021-07
|
|
||||||
# though.)
|
|
||||||
if billing_month != "2021-07":
|
|
||||||
project = "Corona"
|
|
||||||
elif "EBS:VolumeUsage" in usage_type:
|
|
||||||
category.append("EBS Volume")
|
|
||||||
category.extend(["EBS Volume", re.sub(r"^.+\.", "", usage_type)])
|
|
||||||
elif "EBS:SnapshotUsage" in usage_type:
|
|
||||||
category.append("EBS Snapshot")
|
|
||||||
elif (
|
|
||||||
"DataTransfer" in usage_type
|
|
||||||
or "In-Bytes" in usage_type
|
|
||||||
or "Out-Bytes" in usage_type
|
|
||||||
):
|
|
||||||
category.append("Data Transfer")
|
|
||||||
elif "BoxUsage" in usage_type or "CPUCredits" in usage_type:
|
|
||||||
category.extend(["Instance", re.sub(r"^.+:", "", usage_type)])
|
|
||||||
else:
|
|
||||||
category.extend(
|
|
||||||
[
|
|
||||||
"Uncategorized",
|
|
||||||
usage_type,
|
|
||||||
operation or "(no operation)",
|
|
||||||
resource or "(no resource)",
|
|
||||||
]
|
|
||||||
)
|
|
||||||
elif service == "AWSELB":
|
|
||||||
category = ["ELB"]
|
|
||||||
if "DataTransfer" in usage_type:
|
|
||||||
category.append("Data Transfer")
|
|
||||||
elif "LCUUsage" in usage_type:
|
|
||||||
category.append("LCUs")
|
|
||||||
elif "LoadBalancerUsage":
|
|
||||||
category.append("Load Balancer")
|
|
||||||
else:
|
|
||||||
category.extend(
|
|
||||||
[
|
|
||||||
"Uncategorized",
|
|
||||||
usage_type,
|
|
||||||
operation or "(no operation)",
|
|
||||||
resource or "(no resource)",
|
|
||||||
]
|
|
||||||
)
|
|
||||||
elif service == "AmazonCloudWatch":
|
|
||||||
category = ["CloudWatch"]
|
|
||||||
elif service == "awskms":
|
|
||||||
category = ["KMS"]
|
|
||||||
if not project:
|
|
||||||
category.extend(
|
|
||||||
[
|
|
||||||
usage_type,
|
|
||||||
operation or "(no operation)",
|
|
||||||
resource or "(no resource)",
|
|
||||||
]
|
|
||||||
)
|
|
||||||
return [project or "Uncategorized", *category]
|
|
||||||
|
|
||||||
|
|
||||||
def add_to_taxonomy(taxonomy, category, item):
|
|
||||||
if category:
|
|
||||||
categories = taxonomy.setdefault("categories", {})
|
|
||||||
add_to_taxonomy(categories.setdefault(category[0], {}), category[1:], item)
|
|
||||||
else:
|
|
||||||
taxonomy.setdefault("items", []).append(item)
|
|
||||||
taxonomy.setdefault("cost", 0)
|
|
||||||
taxonomy["cost"] += float(item["lineItem/UnblendedCost"])
|
|
||||||
|
|
||||||
|
|
||||||
def uncategorized_last(key):
|
|
||||||
return (key == "Uncategorized", key)
|
|
||||||
|
|
||||||
|
|
||||||
def print_taxonomy(taxonomy, indent="", file=sys.stdout):
|
|
||||||
cost = taxonomy["cost"]
|
|
||||||
categories = taxonomy.get("categories", {})
|
|
||||||
for category in sorted(categories, key=uncategorized_last):
|
|
||||||
subtaxonomy = categories[category]
|
|
||||||
cost = subtaxonomy["cost"]
|
|
||||||
if cost < 0.01:
|
|
||||||
continue
|
|
||||||
print(f"{indent}{category} :: ${cost:.2f}", file=file)
|
|
||||||
print_taxonomy(subtaxonomy, indent=indent + " ", file=file)
|
|
||||||
|
|
||||||
|
|
||||||
def classify_costs(csv_path, **kwargs):
|
|
||||||
all_items = [item for item in read_csv(csv_path)]
|
|
||||||
items = []
|
|
||||||
for item in all_items:
|
|
||||||
cost = item["lineItem/UnblendedCost"]
|
|
||||||
if cost and float(cost):
|
|
||||||
items.append({**item, "lineItem/UnblendedCost": float(cost)})
|
|
||||||
taxonomy = {}
|
|
||||||
for item in embed_taxes(items):
|
|
||||||
add_to_taxonomy(taxonomy, ["AWS", *classify_line_item(item, **kwargs)], item)
|
|
||||||
return taxonomy
|
|
||||||
|
|
||||||
|
|
||||||
def main():
|
|
||||||
parser = argparse.ArgumentParser()
|
|
||||||
parser.add_argument("date")
|
|
||||||
parser.add_argument("-f", "--force-download", action="store_true")
|
|
||||||
parser.add_argument("-w", "--write", action="store_true")
|
|
||||||
args = parser.parse_args()
|
|
||||||
year, month = map(int, args.date.split("-"))
|
|
||||||
billing_month = f"{year}-{month:02d}"
|
|
||||||
csv_path = get_csv(year, month, force_download=args.force_download)
|
|
||||||
taxonomy = classify_costs(csv_path, billing_month=billing_month)
|
|
||||||
print_taxonomy(taxonomy)
|
|
||||||
if args.write:
|
|
||||||
riju_taxonomy = taxonomy["categories"]["AWS"]
|
|
||||||
riju_taxonomy["categories"] = {"Riju": riju_taxonomy["categories"]["Riju"]}
|
|
||||||
target_dir = ROOT / f"{year}-{month:02d}"
|
|
||||||
with open(target_dir / "breakdown.txt", "w") as f:
|
|
||||||
print_taxonomy(riju_taxonomy, file=f)
|
|
||||||
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
|
||||||
main()
|
|
||||||
sys.exit(0)
|
|
|
@ -1,135 +0,0 @@
|
||||||
[[package]]
|
|
||||||
name = "boto3"
|
|
||||||
version = "1.18.23"
|
|
||||||
description = "The AWS SDK for Python"
|
|
||||||
category = "main"
|
|
||||||
optional = false
|
|
||||||
python-versions = ">= 3.6"
|
|
||||||
|
|
||||||
[package.dependencies]
|
|
||||||
botocore = ">=1.21.23,<1.22.0"
|
|
||||||
jmespath = ">=0.7.1,<1.0.0"
|
|
||||||
s3transfer = ">=0.5.0,<0.6.0"
|
|
||||||
|
|
||||||
[package.extras]
|
|
||||||
crt = ["botocore[crt] (>=1.21.0,<2.0a0)"]
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "botocore"
|
|
||||||
version = "1.21.23"
|
|
||||||
description = "Low-level, data-driven core of boto 3."
|
|
||||||
category = "main"
|
|
||||||
optional = false
|
|
||||||
python-versions = ">= 3.6"
|
|
||||||
|
|
||||||
[package.dependencies]
|
|
||||||
jmespath = ">=0.7.1,<1.0.0"
|
|
||||||
python-dateutil = ">=2.1,<3.0.0"
|
|
||||||
urllib3 = ">=1.25.4,<1.27"
|
|
||||||
|
|
||||||
[package.extras]
|
|
||||||
crt = ["awscrt (==0.11.24)"]
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "jmespath"
|
|
||||||
version = "0.10.0"
|
|
||||||
description = "JSON Matching Expressions"
|
|
||||||
category = "main"
|
|
||||||
optional = false
|
|
||||||
python-versions = ">=2.6, !=3.0.*, !=3.1.*, !=3.2.*"
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "python-dateutil"
|
|
||||||
version = "2.8.2"
|
|
||||||
description = "Extensions to the standard Python datetime module"
|
|
||||||
category = "main"
|
|
||||||
optional = false
|
|
||||||
python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,>=2.7"
|
|
||||||
|
|
||||||
[package.dependencies]
|
|
||||||
six = ">=1.5"
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "python-dotenv"
|
|
||||||
version = "0.19.0"
|
|
||||||
description = "Read key-value pairs from a .env file and set them as environment variables"
|
|
||||||
category = "main"
|
|
||||||
optional = false
|
|
||||||
python-versions = ">=3.5"
|
|
||||||
|
|
||||||
[package.extras]
|
|
||||||
cli = ["click (>=5.0)"]
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "s3transfer"
|
|
||||||
version = "0.5.0"
|
|
||||||
description = "An Amazon S3 Transfer Manager"
|
|
||||||
category = "main"
|
|
||||||
optional = false
|
|
||||||
python-versions = ">= 3.6"
|
|
||||||
|
|
||||||
[package.dependencies]
|
|
||||||
botocore = ">=1.12.36,<2.0a.0"
|
|
||||||
|
|
||||||
[package.extras]
|
|
||||||
crt = ["botocore[crt] (>=1.20.29,<2.0a.0)"]
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "six"
|
|
||||||
version = "1.16.0"
|
|
||||||
description = "Python 2 and 3 compatibility utilities"
|
|
||||||
category = "main"
|
|
||||||
optional = false
|
|
||||||
python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*"
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "urllib3"
|
|
||||||
version = "1.26.6"
|
|
||||||
description = "HTTP library with thread-safe connection pooling, file post, and more."
|
|
||||||
category = "main"
|
|
||||||
optional = false
|
|
||||||
python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*, <4"
|
|
||||||
|
|
||||||
[package.extras]
|
|
||||||
brotli = ["brotlipy (>=0.6.0)"]
|
|
||||||
secure = ["pyOpenSSL (>=0.14)", "cryptography (>=1.3.4)", "idna (>=2.0.0)", "certifi", "ipaddress"]
|
|
||||||
socks = ["PySocks (>=1.5.6,!=1.5.7,<2.0)"]
|
|
||||||
|
|
||||||
[metadata]
|
|
||||||
lock-version = "1.1"
|
|
||||||
python-versions = "^3.9"
|
|
||||||
content-hash = "170b0bcf9f0ae12c4c9e1daa195ecdb39585494414b88e53e3da72916eb52c51"
|
|
||||||
|
|
||||||
[metadata.files]
|
|
||||||
boto3 = [
|
|
||||||
{file = "boto3-1.18.23-py3-none-any.whl", hash = "sha256:1b08ace99e7b92965780e5ce759430ad62b7b7e037560bc772f9a8789f4f36d2"},
|
|
||||||
{file = "boto3-1.18.23.tar.gz", hash = "sha256:31cc69e665f773390c4c17ce340d2420e45fbac51d46d945cc4a58d483ec5da6"},
|
|
||||||
]
|
|
||||||
botocore = [
|
|
||||||
{file = "botocore-1.21.23-py3-none-any.whl", hash = "sha256:3877d69e0b718b786f1696cd04ddbdb3a57aef6adb0239a29aa88754489849a4"},
|
|
||||||
{file = "botocore-1.21.23.tar.gz", hash = "sha256:d0146d31dbc475942b578b47dd5bcf94d18fbce8c6d2ce5f12195e005de9b754"},
|
|
||||||
]
|
|
||||||
jmespath = [
|
|
||||||
{file = "jmespath-0.10.0-py2.py3-none-any.whl", hash = "sha256:cdf6525904cc597730141d61b36f2e4b8ecc257c420fa2f4549bac2c2d0cb72f"},
|
|
||||||
{file = "jmespath-0.10.0.tar.gz", hash = "sha256:b85d0567b8666149a93172712e68920734333c0ce7e89b78b3e987f71e5ed4f9"},
|
|
||||||
]
|
|
||||||
python-dateutil = [
|
|
||||||
{file = "python-dateutil-2.8.2.tar.gz", hash = "sha256:0123cacc1627ae19ddf3c27a5de5bd67ee4586fbdd6440d9748f8abb483d3e86"},
|
|
||||||
{file = "python_dateutil-2.8.2-py2.py3-none-any.whl", hash = "sha256:961d03dc3453ebbc59dbdea9e4e11c5651520a876d0f4db161e8674aae935da9"},
|
|
||||||
]
|
|
||||||
python-dotenv = [
|
|
||||||
{file = "python-dotenv-0.19.0.tar.gz", hash = "sha256:f521bc2ac9a8e03c736f62911605c5d83970021e3fa95b37d769e2bbbe9b6172"},
|
|
||||||
{file = "python_dotenv-0.19.0-py2.py3-none-any.whl", hash = "sha256:aae25dc1ebe97c420f50b81fb0e5c949659af713f31fdb63c749ca68748f34b1"},
|
|
||||||
]
|
|
||||||
s3transfer = [
|
|
||||||
{file = "s3transfer-0.5.0-py3-none-any.whl", hash = "sha256:9c1dc369814391a6bda20ebbf4b70a0f34630592c9aa520856bf384916af2803"},
|
|
||||||
{file = "s3transfer-0.5.0.tar.gz", hash = "sha256:50ed823e1dc5868ad40c8dc92072f757aa0e653a192845c94a3b676f4a62da4c"},
|
|
||||||
]
|
|
||||||
six = [
|
|
||||||
{file = "six-1.16.0-py2.py3-none-any.whl", hash = "sha256:8abb2f1d86890a2dfb989f9a77cfcfd3e47c2a354b01111771326f8aa26e0254"},
|
|
||||||
{file = "six-1.16.0.tar.gz", hash = "sha256:1e61c37477a1626458e36f7b1d82aa5c9b094fa4802892072e49de9c60c4c926"},
|
|
||||||
]
|
|
||||||
urllib3 = [
|
|
||||||
{file = "urllib3-1.26.6-py2.py3-none-any.whl", hash = "sha256:39fb8672126159acb139a7718dd10806104dec1e2f0f6c88aab05d17df10c8d4"},
|
|
||||||
{file = "urllib3-1.26.6.tar.gz", hash = "sha256:f57b4c16c62fa2760b7e3d97c35b255512fb6b59a259730f36ba32ce9f8e342f"},
|
|
||||||
]
|
|
|
@ -1,16 +0,0 @@
|
||||||
[tool.poetry]
|
|
||||||
name = "riju-financials"
|
|
||||||
version = "0.1.0"
|
|
||||||
description = "Financial data for Riju hosting"
|
|
||||||
authors = ["Radon Rosborough <radon.neon@gmail.com>"]
|
|
||||||
|
|
||||||
[tool.poetry.dependencies]
|
|
||||||
python = "^3.9"
|
|
||||||
boto3 = "^1.18.23"
|
|
||||||
python-dotenv = "^0.19.0"
|
|
||||||
|
|
||||||
[tool.poetry.dev-dependencies]
|
|
||||||
|
|
||||||
[build-system]
|
|
||||||
requires = ["poetry-core>=1.0.0"]
|
|
||||||
build-backend = "poetry.core.masonry.api"
|
|
|
@ -25,9 +25,11 @@
|
||||||
<p>
|
<p>
|
||||||
<i>
|
<i>
|
||||||
Created by
|
Created by
|
||||||
<a href="https://github.com/raxod502">Radon Rosborough</a>.
|
<a href="https://github.com/raxod502">Radon Rosborough</a>
|
||||||
|
and maintained by
|
||||||
|
<a href="https://radian.codes">Radian LLC</a>.
|
||||||
Check out the project
|
Check out the project
|
||||||
<a href="https://github.com/raxod502/riju">on GitHub</a>.
|
<a href="https://github.com/radian-software/riju">on GitHub</a>.
|
||||||
</i>
|
</i>
|
||||||
</p>
|
</p>
|
||||||
<% } else { %>
|
<% } else { %>
|
||||||
|
|
|
@ -0,0 +1,6 @@
|
||||||
|
receivers:
|
||||||
|
- name: pagerduty
|
||||||
|
pagerduty_configs:
|
||||||
|
- routing_key: "$PAGERDUTY_INTEGRATION_KEY"
|
||||||
|
route:
|
||||||
|
receiver: pagerduty
|
|
@ -0,0 +1,28 @@
|
||||||
|
namespace: riju
|
||||||
|
groups:
|
||||||
|
- name: riju
|
||||||
|
rules:
|
||||||
|
- alert: NodeCPUHigh
|
||||||
|
annotations:
|
||||||
|
message: "Instance {{ $labels.node }} is running close to max CPU"
|
||||||
|
expr: |
|
||||||
|
sum(1 - rate(node_cpu_seconds_total{mode="idle"}[1m])) by (node) / count(sum(node_cpu_seconds_total{mode="idle"}) by (node, cpu)) by (node) * 100 >= 80
|
||||||
|
for: 30m
|
||||||
|
- alert: NodeMemoryHigh
|
||||||
|
annotations:
|
||||||
|
message: "Instance {{ $labels.node }} is running close to max memory"
|
||||||
|
expr: |
|
||||||
|
sum(1 - node_memory_MemAvailable_bytes / node_memory_MemTotal_bytes) by (node) * 100 >= 80
|
||||||
|
for: 30m
|
||||||
|
- alert: RootVolumeFilling
|
||||||
|
annotations:
|
||||||
|
message: "Root volume on instance {{ $labels.node }} is close to full"
|
||||||
|
expr: |
|
||||||
|
(1 - sum (node_filesystem_free_bytes{mountpoint="/"}) by (node) / sum (node_filesystem_size_bytes{mountpoint="/"}) by (node)) * 100 >= 80
|
||||||
|
for: 30m
|
||||||
|
- alert: DataVolumeFilling
|
||||||
|
annotations:
|
||||||
|
message: "Data volume on instance {{ $labels.node }} is close to full"
|
||||||
|
expr: |
|
||||||
|
(1 - sum (node_filesystem_free_bytes{mountpoint="/mnt/riju"}) by (node) / sum (node_filesystem_size_bytes{mountpoint="/mnt/riju"}) by (node)) * 100 >= 80
|
||||||
|
for: 30m
|
|
@ -0,0 +1,902 @@
|
||||||
|
{
|
||||||
|
"annotations": {
|
||||||
|
"list": [
|
||||||
|
{
|
||||||
|
"builtIn": 1,
|
||||||
|
"datasource": "-- Grafana --",
|
||||||
|
"enable": true,
|
||||||
|
"hide": true,
|
||||||
|
"iconColor": "rgba(0, 211, 255, 1)",
|
||||||
|
"name": "Annotations & Alerts",
|
||||||
|
"target": {
|
||||||
|
"limit": 100,
|
||||||
|
"matchAny": false,
|
||||||
|
"tags": [],
|
||||||
|
"type": "dashboard"
|
||||||
|
},
|
||||||
|
"type": "dashboard"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"editable": true,
|
||||||
|
"fiscalYearStartMonth": 0,
|
||||||
|
"graphTooltip": 0,
|
||||||
|
"id": 4,
|
||||||
|
"iteration": 1644689175462,
|
||||||
|
"links": [],
|
||||||
|
"liveNow": false,
|
||||||
|
"panels": [
|
||||||
|
{
|
||||||
|
"gridPos": {
|
||||||
|
"h": 1,
|
||||||
|
"w": 24,
|
||||||
|
"x": 0,
|
||||||
|
"y": 0
|
||||||
|
},
|
||||||
|
"id": 15,
|
||||||
|
"title": "Server metrics",
|
||||||
|
"type": "row"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"datasource": {
|
||||||
|
"type": "prometheus",
|
||||||
|
"uid": "grafanacloud-prom"
|
||||||
|
},
|
||||||
|
"fieldConfig": {
|
||||||
|
"defaults": {
|
||||||
|
"color": {
|
||||||
|
"mode": "palette-classic"
|
||||||
|
},
|
||||||
|
"custom": {
|
||||||
|
"axisLabel": "",
|
||||||
|
"axisPlacement": "auto",
|
||||||
|
"barAlignment": 0,
|
||||||
|
"drawStyle": "line",
|
||||||
|
"fillOpacity": 0,
|
||||||
|
"gradientMode": "none",
|
||||||
|
"hideFrom": {
|
||||||
|
"legend": false,
|
||||||
|
"tooltip": false,
|
||||||
|
"viz": false
|
||||||
|
},
|
||||||
|
"lineInterpolation": "linear",
|
||||||
|
"lineWidth": 1,
|
||||||
|
"pointSize": 5,
|
||||||
|
"scaleDistribution": {
|
||||||
|
"type": "linear"
|
||||||
|
},
|
||||||
|
"showPoints": "auto",
|
||||||
|
"spanNulls": false,
|
||||||
|
"stacking": {
|
||||||
|
"group": "A",
|
||||||
|
"mode": "none"
|
||||||
|
},
|
||||||
|
"thresholdsStyle": {
|
||||||
|
"mode": "off"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"mappings": [],
|
||||||
|
"max": 100,
|
||||||
|
"min": 0,
|
||||||
|
"thresholds": {
|
||||||
|
"mode": "absolute",
|
||||||
|
"steps": [
|
||||||
|
{
|
||||||
|
"color": "green",
|
||||||
|
"value": null
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"color": "red",
|
||||||
|
"value": 80
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"unit": "percent"
|
||||||
|
},
|
||||||
|
"overrides": []
|
||||||
|
},
|
||||||
|
"gridPos": {
|
||||||
|
"h": 8,
|
||||||
|
"w": 12,
|
||||||
|
"x": 0,
|
||||||
|
"y": 1
|
||||||
|
},
|
||||||
|
"id": 16,
|
||||||
|
"options": {
|
||||||
|
"legend": {
|
||||||
|
"calcs": [],
|
||||||
|
"displayMode": "list",
|
||||||
|
"placement": "bottom"
|
||||||
|
},
|
||||||
|
"tooltip": {
|
||||||
|
"mode": "single"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"targets": [
|
||||||
|
{
|
||||||
|
"datasource": {
|
||||||
|
"type": "prometheus",
|
||||||
|
"uid": "grafanacloud-prom"
|
||||||
|
},
|
||||||
|
"exemplar": true,
|
||||||
|
"expr": "sum(rate(process_cpu_seconds_total{node=~\"$node\",job=\"server\"}[1m])) by (node) / count(sum(node_cpu_seconds_total{node=~\"$node\",mode=\"idle\"}) by (node, cpu)) by (node) * 100",
|
||||||
|
"hide": false,
|
||||||
|
"interval": "",
|
||||||
|
"legendFormat": "",
|
||||||
|
"refId": "B"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"title": "CPU Utilization",
|
||||||
|
"type": "timeseries"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"datasource": {
|
||||||
|
"type": "prometheus",
|
||||||
|
"uid": "grafanacloud-prom"
|
||||||
|
},
|
||||||
|
"fieldConfig": {
|
||||||
|
"defaults": {
|
||||||
|
"color": {
|
||||||
|
"mode": "palette-classic"
|
||||||
|
},
|
||||||
|
"custom": {
|
||||||
|
"axisLabel": "",
|
||||||
|
"axisPlacement": "auto",
|
||||||
|
"barAlignment": 0,
|
||||||
|
"drawStyle": "line",
|
||||||
|
"fillOpacity": 0,
|
||||||
|
"gradientMode": "none",
|
||||||
|
"hideFrom": {
|
||||||
|
"legend": false,
|
||||||
|
"tooltip": false,
|
||||||
|
"viz": false
|
||||||
|
},
|
||||||
|
"lineInterpolation": "linear",
|
||||||
|
"lineWidth": 1,
|
||||||
|
"pointSize": 5,
|
||||||
|
"scaleDistribution": {
|
||||||
|
"type": "linear"
|
||||||
|
},
|
||||||
|
"showPoints": "auto",
|
||||||
|
"spanNulls": false,
|
||||||
|
"stacking": {
|
||||||
|
"group": "A",
|
||||||
|
"mode": "none"
|
||||||
|
},
|
||||||
|
"thresholdsStyle": {
|
||||||
|
"mode": "off"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"mappings": [],
|
||||||
|
"max": 100,
|
||||||
|
"min": 0,
|
||||||
|
"thresholds": {
|
||||||
|
"mode": "absolute",
|
||||||
|
"steps": [
|
||||||
|
{
|
||||||
|
"color": "green",
|
||||||
|
"value": null
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"color": "red",
|
||||||
|
"value": 80
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"unit": "percent"
|
||||||
|
},
|
||||||
|
"overrides": []
|
||||||
|
},
|
||||||
|
"gridPos": {
|
||||||
|
"h": 8,
|
||||||
|
"w": 12,
|
||||||
|
"x": 12,
|
||||||
|
"y": 1
|
||||||
|
},
|
||||||
|
"id": 17,
|
||||||
|
"options": {
|
||||||
|
"legend": {
|
||||||
|
"calcs": [],
|
||||||
|
"displayMode": "list",
|
||||||
|
"placement": "bottom"
|
||||||
|
},
|
||||||
|
"tooltip": {
|
||||||
|
"mode": "single"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"targets": [
|
||||||
|
{
|
||||||
|
"datasource": {
|
||||||
|
"type": "prometheus",
|
||||||
|
"uid": "grafanacloud-prom"
|
||||||
|
},
|
||||||
|
"exemplar": true,
|
||||||
|
"expr": "sum(process_resident_memory_bytes{node=~\"$node\",job=\"server\"} / node_memory_MemTotal_bytes{node=~\"$node\"}) by (node) * 100",
|
||||||
|
"hide": false,
|
||||||
|
"interval": "",
|
||||||
|
"legendFormat": "",
|
||||||
|
"refId": "A"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"title": "Memory Utilization",
|
||||||
|
"type": "timeseries"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"collapsed": false,
|
||||||
|
"gridPos": {
|
||||||
|
"h": 1,
|
||||||
|
"w": 24,
|
||||||
|
"x": 0,
|
||||||
|
"y": 9
|
||||||
|
},
|
||||||
|
"id": 6,
|
||||||
|
"panels": [],
|
||||||
|
"title": "Instance metrics",
|
||||||
|
"type": "row"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"datasource": {
|
||||||
|
"type": "prometheus",
|
||||||
|
"uid": "grafanacloud-prom"
|
||||||
|
},
|
||||||
|
"fieldConfig": {
|
||||||
|
"defaults": {
|
||||||
|
"color": {
|
||||||
|
"mode": "palette-classic"
|
||||||
|
},
|
||||||
|
"custom": {
|
||||||
|
"axisLabel": "",
|
||||||
|
"axisPlacement": "auto",
|
||||||
|
"barAlignment": 0,
|
||||||
|
"drawStyle": "line",
|
||||||
|
"fillOpacity": 0,
|
||||||
|
"gradientMode": "none",
|
||||||
|
"hideFrom": {
|
||||||
|
"legend": false,
|
||||||
|
"tooltip": false,
|
||||||
|
"viz": false
|
||||||
|
},
|
||||||
|
"lineInterpolation": "linear",
|
||||||
|
"lineWidth": 1,
|
||||||
|
"pointSize": 5,
|
||||||
|
"scaleDistribution": {
|
||||||
|
"type": "linear"
|
||||||
|
},
|
||||||
|
"showPoints": "auto",
|
||||||
|
"spanNulls": false,
|
||||||
|
"stacking": {
|
||||||
|
"group": "A",
|
||||||
|
"mode": "none"
|
||||||
|
},
|
||||||
|
"thresholdsStyle": {
|
||||||
|
"mode": "off"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"mappings": [],
|
||||||
|
"max": 100,
|
||||||
|
"min": 0,
|
||||||
|
"thresholds": {
|
||||||
|
"mode": "absolute",
|
||||||
|
"steps": [
|
||||||
|
{
|
||||||
|
"color": "green",
|
||||||
|
"value": null
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"color": "red",
|
||||||
|
"value": 80
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"unit": "percent"
|
||||||
|
},
|
||||||
|
"overrides": []
|
||||||
|
},
|
||||||
|
"gridPos": {
|
||||||
|
"h": 8,
|
||||||
|
"w": 12,
|
||||||
|
"x": 0,
|
||||||
|
"y": 10
|
||||||
|
},
|
||||||
|
"id": 8,
|
||||||
|
"options": {
|
||||||
|
"legend": {
|
||||||
|
"calcs": [],
|
||||||
|
"displayMode": "list",
|
||||||
|
"placement": "bottom"
|
||||||
|
},
|
||||||
|
"tooltip": {
|
||||||
|
"mode": "single"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"targets": [
|
||||||
|
{
|
||||||
|
"datasource": {
|
||||||
|
"type": "prometheus",
|
||||||
|
"uid": "grafanacloud-prom"
|
||||||
|
},
|
||||||
|
"exemplar": true,
|
||||||
|
"expr": "sum(1 - rate(node_cpu_seconds_total{node=~\"$node\",mode=\"idle\"}[1m])) by (node) / count(sum(node_cpu_seconds_total{node=~\"$node\",mode=\"idle\"}) by (node, cpu)) by (node) * 100",
|
||||||
|
"interval": "",
|
||||||
|
"legendFormat": "",
|
||||||
|
"refId": "A"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"title": "CPU Utilization",
|
||||||
|
"type": "timeseries"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"datasource": {
|
||||||
|
"type": "prometheus",
|
||||||
|
"uid": "grafanacloud-prom"
|
||||||
|
},
|
||||||
|
"fieldConfig": {
|
||||||
|
"defaults": {
|
||||||
|
"color": {
|
||||||
|
"mode": "palette-classic"
|
||||||
|
},
|
||||||
|
"custom": {
|
||||||
|
"axisLabel": "",
|
||||||
|
"axisPlacement": "auto",
|
||||||
|
"barAlignment": 0,
|
||||||
|
"drawStyle": "line",
|
||||||
|
"fillOpacity": 0,
|
||||||
|
"gradientMode": "none",
|
||||||
|
"hideFrom": {
|
||||||
|
"legend": false,
|
||||||
|
"tooltip": false,
|
||||||
|
"viz": false
|
||||||
|
},
|
||||||
|
"lineInterpolation": "linear",
|
||||||
|
"lineWidth": 1,
|
||||||
|
"pointSize": 5,
|
||||||
|
"scaleDistribution": {
|
||||||
|
"type": "linear"
|
||||||
|
},
|
||||||
|
"showPoints": "auto",
|
||||||
|
"spanNulls": false,
|
||||||
|
"stacking": {
|
||||||
|
"group": "A",
|
||||||
|
"mode": "none"
|
||||||
|
},
|
||||||
|
"thresholdsStyle": {
|
||||||
|
"mode": "off"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"mappings": [],
|
||||||
|
"max": 100,
|
||||||
|
"min": 0,
|
||||||
|
"thresholds": {
|
||||||
|
"mode": "absolute",
|
||||||
|
"steps": [
|
||||||
|
{
|
||||||
|
"color": "green",
|
||||||
|
"value": null
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"color": "red",
|
||||||
|
"value": 80
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"unit": "percent"
|
||||||
|
},
|
||||||
|
"overrides": []
|
||||||
|
},
|
||||||
|
"gridPos": {
|
||||||
|
"h": 8,
|
||||||
|
"w": 12,
|
||||||
|
"x": 12,
|
||||||
|
"y": 10
|
||||||
|
},
|
||||||
|
"id": 9,
|
||||||
|
"options": {
|
||||||
|
"legend": {
|
||||||
|
"calcs": [],
|
||||||
|
"displayMode": "list",
|
||||||
|
"placement": "bottom"
|
||||||
|
},
|
||||||
|
"tooltip": {
|
||||||
|
"mode": "single"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"targets": [
|
||||||
|
{
|
||||||
|
"datasource": {
|
||||||
|
"type": "prometheus",
|
||||||
|
"uid": "grafanacloud-prom"
|
||||||
|
},
|
||||||
|
"exemplar": true,
|
||||||
|
"expr": "sum(1 - node_memory_MemAvailable_bytes{node=~\"$node\"} / node_memory_MemTotal_bytes{node=~\"$node\"}) by (node) * 100",
|
||||||
|
"hide": false,
|
||||||
|
"interval": "",
|
||||||
|
"legendFormat": "",
|
||||||
|
"refId": "B"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"title": "Memory Utilization",
|
||||||
|
"type": "timeseries"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"datasource": {
|
||||||
|
"type": "prometheus",
|
||||||
|
"uid": "grafanacloud-prom"
|
||||||
|
},
|
||||||
|
"fieldConfig": {
|
||||||
|
"defaults": {
|
||||||
|
"color": {
|
||||||
|
"mode": "palette-classic"
|
||||||
|
},
|
||||||
|
"custom": {
|
||||||
|
"axisLabel": "",
|
||||||
|
"axisPlacement": "auto",
|
||||||
|
"barAlignment": 0,
|
||||||
|
"drawStyle": "line",
|
||||||
|
"fillOpacity": 0,
|
||||||
|
"gradientMode": "none",
|
||||||
|
"hideFrom": {
|
||||||
|
"legend": false,
|
||||||
|
"tooltip": false,
|
||||||
|
"viz": false
|
||||||
|
},
|
||||||
|
"lineInterpolation": "linear",
|
||||||
|
"lineWidth": 1,
|
||||||
|
"pointSize": 5,
|
||||||
|
"scaleDistribution": {
|
||||||
|
"type": "linear"
|
||||||
|
},
|
||||||
|
"showPoints": "auto",
|
||||||
|
"spanNulls": false,
|
||||||
|
"stacking": {
|
||||||
|
"group": "A",
|
||||||
|
"mode": "none"
|
||||||
|
},
|
||||||
|
"thresholdsStyle": {
|
||||||
|
"mode": "off"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"mappings": [],
|
||||||
|
"min": 0,
|
||||||
|
"thresholds": {
|
||||||
|
"mode": "absolute",
|
||||||
|
"steps": [
|
||||||
|
{
|
||||||
|
"color": "green",
|
||||||
|
"value": null
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"color": "red",
|
||||||
|
"value": 80
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"unit": "MBs"
|
||||||
|
},
|
||||||
|
"overrides": []
|
||||||
|
},
|
||||||
|
"gridPos": {
|
||||||
|
"h": 8,
|
||||||
|
"w": 12,
|
||||||
|
"x": 0,
|
||||||
|
"y": 18
|
||||||
|
},
|
||||||
|
"id": 10,
|
||||||
|
"options": {
|
||||||
|
"legend": {
|
||||||
|
"calcs": [],
|
||||||
|
"displayMode": "list",
|
||||||
|
"placement": "bottom"
|
||||||
|
},
|
||||||
|
"tooltip": {
|
||||||
|
"mode": "single"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"targets": [
|
||||||
|
{
|
||||||
|
"datasource": {
|
||||||
|
"type": "prometheus",
|
||||||
|
"uid": "grafanacloud-prom"
|
||||||
|
},
|
||||||
|
"exemplar": true,
|
||||||
|
"expr": "sum (rate(node_network_receive_bytes_total{node=~\"$node\"}[1m])) by (node) / 1e6",
|
||||||
|
"hide": false,
|
||||||
|
"interval": "",
|
||||||
|
"legendFormat": "",
|
||||||
|
"refId": "A"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"title": "Network Traffic Received",
|
||||||
|
"type": "timeseries"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"datasource": {
|
||||||
|
"type": "prometheus",
|
||||||
|
"uid": "grafanacloud-prom"
|
||||||
|
},
|
||||||
|
"fieldConfig": {
|
||||||
|
"defaults": {
|
||||||
|
"color": {
|
||||||
|
"mode": "palette-classic"
|
||||||
|
},
|
||||||
|
"custom": {
|
||||||
|
"axisLabel": "",
|
||||||
|
"axisPlacement": "auto",
|
||||||
|
"barAlignment": 0,
|
||||||
|
"drawStyle": "line",
|
||||||
|
"fillOpacity": 0,
|
||||||
|
"gradientMode": "none",
|
||||||
|
"hideFrom": {
|
||||||
|
"legend": false,
|
||||||
|
"tooltip": false,
|
||||||
|
"viz": false
|
||||||
|
},
|
||||||
|
"lineInterpolation": "linear",
|
||||||
|
"lineWidth": 1,
|
||||||
|
"pointSize": 5,
|
||||||
|
"scaleDistribution": {
|
||||||
|
"type": "linear"
|
||||||
|
},
|
||||||
|
"showPoints": "auto",
|
||||||
|
"spanNulls": false,
|
||||||
|
"stacking": {
|
||||||
|
"group": "A",
|
||||||
|
"mode": "none"
|
||||||
|
},
|
||||||
|
"thresholdsStyle": {
|
||||||
|
"mode": "off"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"mappings": [],
|
||||||
|
"min": 0,
|
||||||
|
"thresholds": {
|
||||||
|
"mode": "absolute",
|
||||||
|
"steps": [
|
||||||
|
{
|
||||||
|
"color": "green",
|
||||||
|
"value": null
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"color": "red",
|
||||||
|
"value": 80
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"unit": "KBs"
|
||||||
|
},
|
||||||
|
"overrides": []
|
||||||
|
},
|
||||||
|
"gridPos": {
|
||||||
|
"h": 8,
|
||||||
|
"w": 12,
|
||||||
|
"x": 12,
|
||||||
|
"y": 18
|
||||||
|
},
|
||||||
|
"id": 11,
|
||||||
|
"options": {
|
||||||
|
"legend": {
|
||||||
|
"calcs": [],
|
||||||
|
"displayMode": "list",
|
||||||
|
"placement": "bottom"
|
||||||
|
},
|
||||||
|
"tooltip": {
|
||||||
|
"mode": "single"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"targets": [
|
||||||
|
{
|
||||||
|
"datasource": {
|
||||||
|
"type": "prometheus",
|
||||||
|
"uid": "grafanacloud-prom"
|
||||||
|
},
|
||||||
|
"exemplar": true,
|
||||||
|
"expr": "sum (rate(node_network_transmit_bytes_total{node=~\"$node\"}[1m])) by (node) / 1e3",
|
||||||
|
"hide": false,
|
||||||
|
"interval": "",
|
||||||
|
"legendFormat": "",
|
||||||
|
"refId": "A"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"title": "Network Traffic Sent",
|
||||||
|
"type": "timeseries"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"datasource": {
|
||||||
|
"type": "prometheus",
|
||||||
|
"uid": "grafanacloud-prom"
|
||||||
|
},
|
||||||
|
"fieldConfig": {
|
||||||
|
"defaults": {
|
||||||
|
"color": {
|
||||||
|
"mode": "palette-classic"
|
||||||
|
},
|
||||||
|
"custom": {
|
||||||
|
"axisLabel": "",
|
||||||
|
"axisPlacement": "auto",
|
||||||
|
"barAlignment": 0,
|
||||||
|
"drawStyle": "line",
|
||||||
|
"fillOpacity": 0,
|
||||||
|
"gradientMode": "none",
|
||||||
|
"hideFrom": {
|
||||||
|
"legend": false,
|
||||||
|
"tooltip": false,
|
||||||
|
"viz": false
|
||||||
|
},
|
||||||
|
"lineInterpolation": "linear",
|
||||||
|
"lineWidth": 1,
|
||||||
|
"pointSize": 5,
|
||||||
|
"scaleDistribution": {
|
||||||
|
"type": "linear"
|
||||||
|
},
|
||||||
|
"showPoints": "auto",
|
||||||
|
"spanNulls": false,
|
||||||
|
"stacking": {
|
||||||
|
"group": "A",
|
||||||
|
"mode": "none"
|
||||||
|
},
|
||||||
|
"thresholdsStyle": {
|
||||||
|
"mode": "off"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"mappings": [],
|
||||||
|
"max": 100,
|
||||||
|
"min": 0,
|
||||||
|
"thresholds": {
|
||||||
|
"mode": "absolute",
|
||||||
|
"steps": [
|
||||||
|
{
|
||||||
|
"color": "green",
|
||||||
|
"value": null
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"color": "red",
|
||||||
|
"value": 80
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"unit": "percent"
|
||||||
|
},
|
||||||
|
"overrides": []
|
||||||
|
},
|
||||||
|
"gridPos": {
|
||||||
|
"h": 8,
|
||||||
|
"w": 12,
|
||||||
|
"x": 0,
|
||||||
|
"y": 26
|
||||||
|
},
|
||||||
|
"id": 12,
|
||||||
|
"options": {
|
||||||
|
"legend": {
|
||||||
|
"calcs": [],
|
||||||
|
"displayMode": "list",
|
||||||
|
"placement": "bottom"
|
||||||
|
},
|
||||||
|
"tooltip": {
|
||||||
|
"mode": "single"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"targets": [
|
||||||
|
{
|
||||||
|
"datasource": {
|
||||||
|
"type": "prometheus",
|
||||||
|
"uid": "grafanacloud-prom"
|
||||||
|
},
|
||||||
|
"exemplar": true,
|
||||||
|
"expr": "(1 - sum (node_filesystem_free_bytes{node=~\"$node\",mountpoint=\"/\"}) by (node) / sum (node_filesystem_size_bytes{node=~\"$node\",mountpoint=\"/\"}) by (node)) * 100",
|
||||||
|
"hide": false,
|
||||||
|
"interval": "",
|
||||||
|
"legendFormat": "",
|
||||||
|
"refId": "A"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"title": "Root Volume Disk Utilization",
|
||||||
|
"type": "timeseries"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"datasource": {
|
||||||
|
"type": "prometheus",
|
||||||
|
"uid": "grafanacloud-prom"
|
||||||
|
},
|
||||||
|
"fieldConfig": {
|
||||||
|
"defaults": {
|
||||||
|
"color": {
|
||||||
|
"mode": "palette-classic"
|
||||||
|
},
|
||||||
|
"custom": {
|
||||||
|
"axisLabel": "",
|
||||||
|
"axisPlacement": "auto",
|
||||||
|
"barAlignment": 0,
|
||||||
|
"drawStyle": "line",
|
||||||
|
"fillOpacity": 0,
|
||||||
|
"gradientMode": "none",
|
||||||
|
"hideFrom": {
|
||||||
|
"legend": false,
|
||||||
|
"tooltip": false,
|
||||||
|
"viz": false
|
||||||
|
},
|
||||||
|
"lineInterpolation": "linear",
|
||||||
|
"lineWidth": 1,
|
||||||
|
"pointSize": 5,
|
||||||
|
"scaleDistribution": {
|
||||||
|
"type": "linear"
|
||||||
|
},
|
||||||
|
"showPoints": "auto",
|
||||||
|
"spanNulls": false,
|
||||||
|
"stacking": {
|
||||||
|
"group": "A",
|
||||||
|
"mode": "none"
|
||||||
|
},
|
||||||
|
"thresholdsStyle": {
|
||||||
|
"mode": "off"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"mappings": [],
|
||||||
|
"max": 100,
|
||||||
|
"min": 0,
|
||||||
|
"thresholds": {
|
||||||
|
"mode": "absolute",
|
||||||
|
"steps": [
|
||||||
|
{
|
||||||
|
"color": "green",
|
||||||
|
"value": null
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"color": "red",
|
||||||
|
"value": 80
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"unit": "percent"
|
||||||
|
},
|
||||||
|
"overrides": []
|
||||||
|
},
|
||||||
|
"gridPos": {
|
||||||
|
"h": 8,
|
||||||
|
"w": 12,
|
||||||
|
"x": 12,
|
||||||
|
"y": 26
|
||||||
|
},
|
||||||
|
"id": 13,
|
||||||
|
"options": {
|
||||||
|
"legend": {
|
||||||
|
"calcs": [],
|
||||||
|
"displayMode": "list",
|
||||||
|
"placement": "bottom"
|
||||||
|
},
|
||||||
|
"tooltip": {
|
||||||
|
"mode": "single"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"targets": [
|
||||||
|
{
|
||||||
|
"datasource": {
|
||||||
|
"type": "prometheus",
|
||||||
|
"uid": "grafanacloud-prom"
|
||||||
|
},
|
||||||
|
"exemplar": true,
|
||||||
|
"expr": "(1 - sum (node_filesystem_free_bytes{node=~\"$node\",mountpoint=\"/mnt/riju\"}) by (node) / sum (node_filesystem_size_bytes{node=~\"$node\",mountpoint=\"/mnt/riju\"}) by (node)) * 100",
|
||||||
|
"hide": false,
|
||||||
|
"interval": "",
|
||||||
|
"legendFormat": "",
|
||||||
|
"refId": "A"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"title": "Data Volume Disk Utilization",
|
||||||
|
"type": "timeseries"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"collapsed": false,
|
||||||
|
"gridPos": {
|
||||||
|
"h": 1,
|
||||||
|
"w": 24,
|
||||||
|
"x": 0,
|
||||||
|
"y": 34
|
||||||
|
},
|
||||||
|
"id": 2,
|
||||||
|
"panels": [],
|
||||||
|
"title": "Logs",
|
||||||
|
"type": "row"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"datasource": {
|
||||||
|
"type": "loki",
|
||||||
|
"uid": "grafanacloud-logs"
|
||||||
|
},
|
||||||
|
"gridPos": {
|
||||||
|
"h": 12,
|
||||||
|
"w": 24,
|
||||||
|
"x": 0,
|
||||||
|
"y": 35
|
||||||
|
},
|
||||||
|
"id": 4,
|
||||||
|
"options": {
|
||||||
|
"dedupStrategy": "none",
|
||||||
|
"enableLogDetails": true,
|
||||||
|
"prettifyLogMessage": false,
|
||||||
|
"showCommonLabels": false,
|
||||||
|
"showLabels": false,
|
||||||
|
"showTime": true,
|
||||||
|
"sortOrder": "Ascending",
|
||||||
|
"wrapLogMessage": false
|
||||||
|
},
|
||||||
|
"targets": [
|
||||||
|
{
|
||||||
|
"datasource": {
|
||||||
|
"type": "loki",
|
||||||
|
"uid": "grafanacloud-logs"
|
||||||
|
},
|
||||||
|
"expr": "{source=~\"$log_source\",node=~\"$node\"} | regexp \"(?P<log>.+)\" | line_format \"{{ .node }} {{ .log }}\"",
|
||||||
|
"maxLines": 50,
|
||||||
|
"queryType": "randomWalk",
|
||||||
|
"refId": "A"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"title": "Logs",
|
||||||
|
"type": "logs"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"refresh": "5s",
|
||||||
|
"schemaVersion": 34,
|
||||||
|
"style": "dark",
|
||||||
|
"tags": [],
|
||||||
|
"templating": {
|
||||||
|
"list": [
|
||||||
|
{
|
||||||
|
"current": {
|
||||||
|
"selected": false,
|
||||||
|
"text": ["All"],
|
||||||
|
"value": ["$__all"]
|
||||||
|
},
|
||||||
|
"datasource": {
|
||||||
|
"type": "loki",
|
||||||
|
"uid": "grafanacloud-logs"
|
||||||
|
},
|
||||||
|
"definition": "label_values(node)",
|
||||||
|
"hide": 0,
|
||||||
|
"includeAll": true,
|
||||||
|
"label": "",
|
||||||
|
"multi": true,
|
||||||
|
"name": "node",
|
||||||
|
"options": [],
|
||||||
|
"query": "label_values(node)",
|
||||||
|
"refresh": 1,
|
||||||
|
"regex": "",
|
||||||
|
"skipUrlSync": false,
|
||||||
|
"sort": 1,
|
||||||
|
"type": "query"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"current": {
|
||||||
|
"selected": false,
|
||||||
|
"text": ["All"],
|
||||||
|
"value": ["$__all"]
|
||||||
|
},
|
||||||
|
"datasource": {
|
||||||
|
"type": "loki",
|
||||||
|
"uid": "grafanacloud-logs"
|
||||||
|
},
|
||||||
|
"definition": "label_values(source)",
|
||||||
|
"hide": 0,
|
||||||
|
"includeAll": true,
|
||||||
|
"multi": true,
|
||||||
|
"name": "log_source",
|
||||||
|
"options": [],
|
||||||
|
"query": "label_values(source)",
|
||||||
|
"refresh": 1,
|
||||||
|
"regex": "",
|
||||||
|
"skipUrlSync": false,
|
||||||
|
"sort": 1,
|
||||||
|
"type": "query"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"time": {
|
||||||
|
"from": "now-1h",
|
||||||
|
"to": "now"
|
||||||
|
},
|
||||||
|
"timepicker": {},
|
||||||
|
"timezone": "",
|
||||||
|
"title": "Riju",
|
||||||
|
"uid": "mx3ZlzMnk",
|
||||||
|
"version": 30,
|
||||||
|
"weekStart": ""
|
||||||
|
}
|
|
@ -0,0 +1,105 @@
|
||||||
|
apiVersion: k0s.k0sproject.io/v1beta1
|
||||||
|
kind: ClusterConfig
|
||||||
|
metadata:
|
||||||
|
creationTimestamp: null
|
||||||
|
name: k0s
|
||||||
|
spec:
|
||||||
|
api:
|
||||||
|
address: 192.168.0.216
|
||||||
|
k0sApiPort: 9443
|
||||||
|
port: 6443
|
||||||
|
sans:
|
||||||
|
- 192.168.0.216
|
||||||
|
- 192.168.122.1
|
||||||
|
- 172.21.0.1
|
||||||
|
- 172.17.0.1
|
||||||
|
- 172.23.0.1
|
||||||
|
- 10.88.0.1
|
||||||
|
- 10.244.0.1
|
||||||
|
- 2601:646:4000:3060::be49
|
||||||
|
- 2601:646:4000:3060:8b13:5b76:2703:28f
|
||||||
|
- 2601:646:4000:3060:683c:7a51:eee8:2eb
|
||||||
|
- fe80::1fbd:2949:a12e:cedf
|
||||||
|
- fe80::42:d5ff:fe58:a84f
|
||||||
|
- fe80::844c:59ff:fe46:20dc
|
||||||
|
- fe80::20e5:9dff:fe7a:4698
|
||||||
|
- fe80::f86c:22ff:feb0:59ac
|
||||||
|
- fe80::b0d0:51ff:fe45:ce31
|
||||||
|
- fe80::3cd5:9eff:fed1:5f72
|
||||||
|
tunneledNetworkingMode: false
|
||||||
|
controllerManager: {}
|
||||||
|
extensions:
|
||||||
|
helm:
|
||||||
|
charts: null
|
||||||
|
repositories: null
|
||||||
|
storage:
|
||||||
|
type: openebs_local_storage
|
||||||
|
images:
|
||||||
|
calico:
|
||||||
|
cni:
|
||||||
|
image: docker.io/calico/cni
|
||||||
|
version: v3.24.5
|
||||||
|
kubecontrollers:
|
||||||
|
image: docker.io/calico/kube-controllers
|
||||||
|
version: v3.24.5
|
||||||
|
node:
|
||||||
|
image: docker.io/calico/node
|
||||||
|
version: v3.24.5
|
||||||
|
coredns:
|
||||||
|
image: docker.io/coredns/coredns
|
||||||
|
version: 1.9.4
|
||||||
|
default_pull_policy: IfNotPresent
|
||||||
|
konnectivity:
|
||||||
|
image: quay.io/k0sproject/apiserver-network-proxy-agent
|
||||||
|
version: 0.0.32-k0s1
|
||||||
|
kubeproxy:
|
||||||
|
image: registry.k8s.io/kube-proxy
|
||||||
|
version: v1.25.4
|
||||||
|
kuberouter:
|
||||||
|
cni:
|
||||||
|
image: docker.io/cloudnativelabs/kube-router
|
||||||
|
version: v1.5.1
|
||||||
|
cniInstaller:
|
||||||
|
image: quay.io/k0sproject/cni-node
|
||||||
|
version: 1.1.1-k0s.0
|
||||||
|
metricsserver:
|
||||||
|
image: registry.k8s.io/metrics-server/metrics-server
|
||||||
|
version: v0.6.1
|
||||||
|
pushgateway:
|
||||||
|
image: quay.io/k0sproject/pushgateway-ttl
|
||||||
|
version: edge@sha256:7031f6bf6c957e2fdb496161fe3bea0a5bde3de800deeba7b2155187196ecbd9
|
||||||
|
installConfig:
|
||||||
|
users:
|
||||||
|
etcdUser: etcd
|
||||||
|
kineUser: kube-apiserver
|
||||||
|
konnectivityUser: konnectivity-server
|
||||||
|
kubeAPIserverUser: kube-apiserver
|
||||||
|
kubeSchedulerUser: kube-scheduler
|
||||||
|
konnectivity:
|
||||||
|
adminPort: 8133
|
||||||
|
agentPort: 8132
|
||||||
|
network:
|
||||||
|
calico: null
|
||||||
|
clusterDomain: cluster.local
|
||||||
|
dualStack: {}
|
||||||
|
kubeProxy:
|
||||||
|
mode: iptables
|
||||||
|
kuberouter:
|
||||||
|
autoMTU: true
|
||||||
|
hairpinMode: false
|
||||||
|
metricsPort: 8080
|
||||||
|
mtu: 0
|
||||||
|
peerRouterASNs: ""
|
||||||
|
peerRouterIPs: ""
|
||||||
|
podCIDR: 10.244.0.0/16
|
||||||
|
provider: kuberouter
|
||||||
|
serviceCIDR: 10.96.0.0/12
|
||||||
|
scheduler: {}
|
||||||
|
storage:
|
||||||
|
etcd:
|
||||||
|
externalCluster: null
|
||||||
|
peerAddress: 192.168.0.216
|
||||||
|
type: etcd
|
||||||
|
telemetry:
|
||||||
|
enabled: true
|
||||||
|
status: {}
|
|
@ -0,0 +1,9 @@
|
||||||
|
---
|
||||||
|
kind: IPAddressPool
|
||||||
|
apiVersion: metallb.io/v1beta1
|
||||||
|
metadata:
|
||||||
|
namespace: metallb
|
||||||
|
name: self
|
||||||
|
spec:
|
||||||
|
addresses:
|
||||||
|
- "{{ .networking.ip }}/32"
|
File diff suppressed because it is too large
Load Diff
|
@ -0,0 +1,352 @@
|
||||||
|
# Based on bitnami/metallb helm chart 4.1.12 for metallb 0.13.7
|
||||||
|
|
||||||
|
---
|
||||||
|
kind: ServiceAccount
|
||||||
|
apiVersion: v1
|
||||||
|
metadata:
|
||||||
|
namespace: metallb
|
||||||
|
name: metallb-controller
|
||||||
|
automountServiceAccountToken: true
|
||||||
|
|
||||||
|
---
|
||||||
|
kind: ClusterRole
|
||||||
|
apiVersion: rbac.authorization.k8s.io/v1
|
||||||
|
metadata:
|
||||||
|
name: metallb-controller
|
||||||
|
rules:
|
||||||
|
- apiGroups:
|
||||||
|
- ""
|
||||||
|
resources:
|
||||||
|
- services
|
||||||
|
verbs:
|
||||||
|
- get
|
||||||
|
- list
|
||||||
|
- watch
|
||||||
|
- update
|
||||||
|
- apiGroups:
|
||||||
|
- ""
|
||||||
|
resources:
|
||||||
|
- services/status
|
||||||
|
verbs:
|
||||||
|
- update
|
||||||
|
- apiGroups:
|
||||||
|
- ""
|
||||||
|
resources:
|
||||||
|
- events
|
||||||
|
verbs:
|
||||||
|
- create
|
||||||
|
- patch
|
||||||
|
- apiGroups:
|
||||||
|
- policy
|
||||||
|
resourceNames:
|
||||||
|
- metallb-controller
|
||||||
|
resources:
|
||||||
|
- podsecuritypolicies
|
||||||
|
verbs:
|
||||||
|
- use
|
||||||
|
- apiGroups:
|
||||||
|
- admissionregistration.k8s.io
|
||||||
|
resources:
|
||||||
|
- validatingwebhookconfigurations
|
||||||
|
- mutatingwebhookconfigurations
|
||||||
|
verbs:
|
||||||
|
- create
|
||||||
|
- delete
|
||||||
|
- get
|
||||||
|
- list
|
||||||
|
- patch
|
||||||
|
- update
|
||||||
|
- watch
|
||||||
|
- apiGroups:
|
||||||
|
- apiextensions.k8s.io
|
||||||
|
resources:
|
||||||
|
- customresourcedefinitions
|
||||||
|
verbs:
|
||||||
|
- create
|
||||||
|
- delete
|
||||||
|
- get
|
||||||
|
- list
|
||||||
|
- patch
|
||||||
|
- update
|
||||||
|
- watch
|
||||||
|
|
||||||
|
---
|
||||||
|
apiVersion: rbac.authorization.k8s.io/v1
|
||||||
|
kind: ClusterRoleBinding
|
||||||
|
metadata:
|
||||||
|
name: metallb-controller
|
||||||
|
subjects:
|
||||||
|
- kind: ServiceAccount
|
||||||
|
namespace: metallb
|
||||||
|
name: metallb-controller
|
||||||
|
roleRef:
|
||||||
|
kind: ClusterRole
|
||||||
|
apiGroup: rbac.authorization.k8s.io
|
||||||
|
name: metallb-controller
|
||||||
|
|
||||||
|
---
|
||||||
|
kind: Role
|
||||||
|
apiVersion: rbac.authorization.k8s.io/v1
|
||||||
|
metadata:
|
||||||
|
namespace: metallb
|
||||||
|
name: metallb-controller
|
||||||
|
rules:
|
||||||
|
- apiGroups:
|
||||||
|
- ""
|
||||||
|
resources:
|
||||||
|
- secrets
|
||||||
|
verbs:
|
||||||
|
- create
|
||||||
|
- get
|
||||||
|
- list
|
||||||
|
- watch
|
||||||
|
- apiGroups:
|
||||||
|
- ""
|
||||||
|
resources:
|
||||||
|
- secrets
|
||||||
|
resourceNames:
|
||||||
|
- metallb-memberlist
|
||||||
|
verbs:
|
||||||
|
- list
|
||||||
|
- apiGroups:
|
||||||
|
- apps
|
||||||
|
resources:
|
||||||
|
- deployments
|
||||||
|
resourceNames:
|
||||||
|
- metallb-controller
|
||||||
|
verbs:
|
||||||
|
- get
|
||||||
|
- apiGroups:
|
||||||
|
- ""
|
||||||
|
resources:
|
||||||
|
- secrets
|
||||||
|
verbs:
|
||||||
|
- create
|
||||||
|
- delete
|
||||||
|
- get
|
||||||
|
- list
|
||||||
|
- patch
|
||||||
|
- update
|
||||||
|
- watch
|
||||||
|
- apiGroups:
|
||||||
|
- metallb.io
|
||||||
|
resources:
|
||||||
|
- addresspools
|
||||||
|
verbs:
|
||||||
|
- get
|
||||||
|
- list
|
||||||
|
- watch
|
||||||
|
- apiGroups:
|
||||||
|
- metallb.io
|
||||||
|
resources:
|
||||||
|
- ipaddresspools
|
||||||
|
verbs:
|
||||||
|
- get
|
||||||
|
- list
|
||||||
|
- watch
|
||||||
|
- apiGroups:
|
||||||
|
- metallb.io
|
||||||
|
resources:
|
||||||
|
- bgppeers
|
||||||
|
verbs:
|
||||||
|
- get
|
||||||
|
- list
|
||||||
|
- apiGroups:
|
||||||
|
- metallb.io
|
||||||
|
resources:
|
||||||
|
- bgpadvertisements
|
||||||
|
verbs:
|
||||||
|
- get
|
||||||
|
- list
|
||||||
|
- apiGroups:
|
||||||
|
- metallb.io
|
||||||
|
resources:
|
||||||
|
- l2advertisements
|
||||||
|
verbs:
|
||||||
|
- get
|
||||||
|
- list
|
||||||
|
- apiGroups:
|
||||||
|
- metallb.io
|
||||||
|
resources:
|
||||||
|
- communities
|
||||||
|
verbs:
|
||||||
|
- get
|
||||||
|
- list
|
||||||
|
- watch
|
||||||
|
- apiGroups:
|
||||||
|
- metallb.io
|
||||||
|
resources:
|
||||||
|
- bfdprofiles
|
||||||
|
verbs:
|
||||||
|
- get
|
||||||
|
- list
|
||||||
|
- watch
|
||||||
|
|
||||||
|
---
|
||||||
|
kind: RoleBinding
|
||||||
|
apiVersion: rbac.authorization.k8s.io/v1
|
||||||
|
metadata:
|
||||||
|
namespace: metallb
|
||||||
|
name: metallb-controller
|
||||||
|
subjects:
|
||||||
|
- kind: ServiceAccount
|
||||||
|
namespace: metallb
|
||||||
|
name: metallb-controller
|
||||||
|
roleRef:
|
||||||
|
kind: Role
|
||||||
|
apiGroup: rbac.authorization.k8s.io
|
||||||
|
name: metallb-controller
|
||||||
|
|
||||||
|
---
|
||||||
|
kind: ServiceAccount
|
||||||
|
apiVersion: v1
|
||||||
|
metadata:
|
||||||
|
namespace: metallb
|
||||||
|
name: metallb-speaker
|
||||||
|
automountServiceAccountToken: true
|
||||||
|
|
||||||
|
---
|
||||||
|
kind: ClusterRole
|
||||||
|
apiVersion: rbac.authorization.k8s.io/v1
|
||||||
|
metadata:
|
||||||
|
name: metallb-speaker
|
||||||
|
rules:
|
||||||
|
- apiGroups:
|
||||||
|
- ""
|
||||||
|
resources:
|
||||||
|
- services
|
||||||
|
- endpoints
|
||||||
|
- nodes
|
||||||
|
verbs:
|
||||||
|
- get
|
||||||
|
- list
|
||||||
|
- watch
|
||||||
|
- apiGroups:
|
||||||
|
- ""
|
||||||
|
resources:
|
||||||
|
- events
|
||||||
|
verbs:
|
||||||
|
- create
|
||||||
|
- patch
|
||||||
|
- apiGroups:
|
||||||
|
- policy
|
||||||
|
resourceNames:
|
||||||
|
- metallb-speaker
|
||||||
|
resources:
|
||||||
|
- podsecuritypolicies
|
||||||
|
verbs:
|
||||||
|
- use
|
||||||
|
- apiGroups:
|
||||||
|
- discovery.k8s.io
|
||||||
|
resources:
|
||||||
|
- endpointslices
|
||||||
|
verbs:
|
||||||
|
- get
|
||||||
|
- list
|
||||||
|
- watch
|
||||||
|
|
||||||
|
---
|
||||||
|
apiVersion: rbac.authorization.k8s.io/v1
|
||||||
|
kind: ClusterRoleBinding
|
||||||
|
metadata:
|
||||||
|
name: metallb-speaker
|
||||||
|
subjects:
|
||||||
|
- kind: ServiceAccount
|
||||||
|
namespace: metallb
|
||||||
|
name: metallb-speaker
|
||||||
|
roleRef:
|
||||||
|
apiGroup: rbac.authorization.k8s.io
|
||||||
|
kind: ClusterRole
|
||||||
|
name: metallb-speaker
|
||||||
|
|
||||||
|
---
|
||||||
|
kind: Role
|
||||||
|
apiVersion: rbac.authorization.k8s.io/v1
|
||||||
|
metadata:
|
||||||
|
namespace: metallb
|
||||||
|
name: metallb-pod-lister
|
||||||
|
rules:
|
||||||
|
- apiGroups:
|
||||||
|
- ""
|
||||||
|
resources:
|
||||||
|
- pods
|
||||||
|
verbs:
|
||||||
|
- list
|
||||||
|
- apiGroups:
|
||||||
|
- ""
|
||||||
|
resources:
|
||||||
|
- secrets
|
||||||
|
verbs:
|
||||||
|
- get
|
||||||
|
- list
|
||||||
|
- watch
|
||||||
|
- apiGroups:
|
||||||
|
- metallb.io
|
||||||
|
resources:
|
||||||
|
- addresspools
|
||||||
|
verbs:
|
||||||
|
- get
|
||||||
|
- list
|
||||||
|
- watch
|
||||||
|
- apiGroups:
|
||||||
|
- metallb.io
|
||||||
|
resources:
|
||||||
|
- bfdprofiles
|
||||||
|
verbs:
|
||||||
|
- get
|
||||||
|
- list
|
||||||
|
- watch
|
||||||
|
- apiGroups:
|
||||||
|
- metallb.io
|
||||||
|
resources:
|
||||||
|
- bgppeers
|
||||||
|
verbs:
|
||||||
|
- get
|
||||||
|
- list
|
||||||
|
- watch
|
||||||
|
- apiGroups:
|
||||||
|
- metallb.io
|
||||||
|
resources:
|
||||||
|
- l2advertisements
|
||||||
|
verbs:
|
||||||
|
- get
|
||||||
|
- list
|
||||||
|
- watch
|
||||||
|
- apiGroups:
|
||||||
|
- metallb.io
|
||||||
|
resources:
|
||||||
|
- bgpadvertisements
|
||||||
|
verbs:
|
||||||
|
- get
|
||||||
|
- list
|
||||||
|
- watch
|
||||||
|
- apiGroups:
|
||||||
|
- metallb.io
|
||||||
|
resources:
|
||||||
|
- ipaddresspools
|
||||||
|
verbs:
|
||||||
|
- get
|
||||||
|
- list
|
||||||
|
- watch
|
||||||
|
- apiGroups:
|
||||||
|
- metallb.io
|
||||||
|
resources:
|
||||||
|
- communities
|
||||||
|
verbs:
|
||||||
|
- get
|
||||||
|
- list
|
||||||
|
- watch
|
||||||
|
|
||||||
|
---
|
||||||
|
kind: RoleBinding
|
||||||
|
apiVersion: rbac.authorization.k8s.io/v1
|
||||||
|
metadata:
|
||||||
|
namespace: metallb
|
||||||
|
name: metallb-pod-lister
|
||||||
|
roleRef:
|
||||||
|
kind: Role
|
||||||
|
apiGroup: rbac.authorization.k8s.io
|
||||||
|
name: metallb-pod-lister
|
||||||
|
subjects:
|
||||||
|
- kind: ServiceAccount
|
||||||
|
name: metallb-speaker
|
|
@ -0,0 +1,326 @@
|
||||||
|
# Based on bitnami/metallb helm chart 4.1.12 for metallb 0.13.7
|
||||||
|
|
||||||
|
---
|
||||||
|
kind: DaemonSet
|
||||||
|
apiVersion: apps/v1
|
||||||
|
metadata:
|
||||||
|
namespace: metallb
|
||||||
|
name: metallb-speaker
|
||||||
|
spec:
|
||||||
|
updateStrategy:
|
||||||
|
type: RollingUpdate
|
||||||
|
selector:
|
||||||
|
matchLabels:
|
||||||
|
app: metallb-speaker
|
||||||
|
template:
|
||||||
|
metadata:
|
||||||
|
labels:
|
||||||
|
app: metallb-speaker
|
||||||
|
spec:
|
||||||
|
serviceAccountName: metallb-speaker
|
||||||
|
hostNetwork: true
|
||||||
|
securityContext:
|
||||||
|
fsGroup: 0
|
||||||
|
terminationGracePeriodSeconds: 2
|
||||||
|
containers:
|
||||||
|
- name: metallb-speaker
|
||||||
|
image: "docker.io/bitnami/metallb-speaker:0.13.7-debian-11-r8"
|
||||||
|
imagePullPolicy: IfNotPresent
|
||||||
|
securityContext:
|
||||||
|
allowPrivilegeEscalation: false
|
||||||
|
capabilities:
|
||||||
|
add:
|
||||||
|
- NET_ADMIN
|
||||||
|
- NET_RAW
|
||||||
|
- SYS_ADMIN
|
||||||
|
drop:
|
||||||
|
- ALL
|
||||||
|
readOnlyRootFilesystem: true
|
||||||
|
runAsUser: 0
|
||||||
|
args:
|
||||||
|
- "--port=7472"
|
||||||
|
env:
|
||||||
|
- name: METALLB_NODE_NAME
|
||||||
|
valueFrom:
|
||||||
|
fieldRef:
|
||||||
|
fieldPath: spec.nodeName
|
||||||
|
- name: METALLB_HOST
|
||||||
|
valueFrom:
|
||||||
|
fieldRef:
|
||||||
|
fieldPath: status.hostIP
|
||||||
|
- name: METALLB_ML_BIND_ADDR
|
||||||
|
valueFrom:
|
||||||
|
fieldRef:
|
||||||
|
fieldPath: status.podIP
|
||||||
|
- name: METALLB_ML_LABELS
|
||||||
|
value: app=metallb-speaker
|
||||||
|
- name: METALLB_ML_NAMESPACE
|
||||||
|
valueFrom:
|
||||||
|
fieldRef:
|
||||||
|
fieldPath: metadata.namespace
|
||||||
|
- name: METALLB_ML_SECRET_KEY
|
||||||
|
valueFrom:
|
||||||
|
secretKeyRef:
|
||||||
|
name: metallb-memberlist
|
||||||
|
key: secretkey
|
||||||
|
ports:
|
||||||
|
- name: metrics
|
||||||
|
containerPort: 7472
|
||||||
|
livenessProbe:
|
||||||
|
failureThreshold: 3
|
||||||
|
initialDelaySeconds: 10
|
||||||
|
periodSeconds: 10
|
||||||
|
successThreshold: 1
|
||||||
|
timeoutSeconds: 1
|
||||||
|
httpGet:
|
||||||
|
path: /metrics
|
||||||
|
port: metrics
|
||||||
|
readinessProbe:
|
||||||
|
failureThreshold: 3
|
||||||
|
initialDelaySeconds: 10
|
||||||
|
periodSeconds: 10
|
||||||
|
successThreshold: 1
|
||||||
|
timeoutSeconds: 1
|
||||||
|
httpGet:
|
||||||
|
path: /metrics
|
||||||
|
port: metrics
|
||||||
|
resources: {}
|
||||||
|
|
||||||
|
---
|
||||||
|
kind: Secret
|
||||||
|
apiVersion: v1
|
||||||
|
metadata:
|
||||||
|
namespace: metallb
|
||||||
|
name: webhook-server-cert
|
||||||
|
|
||||||
|
---
|
||||||
|
kind: Deployment
|
||||||
|
apiVersion: apps/v1
|
||||||
|
metadata:
|
||||||
|
namespace: metallb
|
||||||
|
name: metallb-controller
|
||||||
|
labels:
|
||||||
|
app.kubernetes.io/name: metallb
|
||||||
|
spec:
|
||||||
|
replicas: 1
|
||||||
|
strategy:
|
||||||
|
type: RollingUpdate
|
||||||
|
revisionHistoryLimit: 3
|
||||||
|
selector:
|
||||||
|
matchLabels:
|
||||||
|
app: metallb-controller
|
||||||
|
template:
|
||||||
|
metadata:
|
||||||
|
labels:
|
||||||
|
app: metallb-controller
|
||||||
|
spec:
|
||||||
|
serviceAccountName: metallb-controller
|
||||||
|
securityContext:
|
||||||
|
fsGroup: 1001
|
||||||
|
volumes:
|
||||||
|
- name: cert
|
||||||
|
secret:
|
||||||
|
defaultMode: 420
|
||||||
|
secretName: webhook-server-cert
|
||||||
|
containers:
|
||||||
|
- name: metallb-controller
|
||||||
|
image: "docker.io/bitnami/metallb-controller:0.13.7-debian-11-r9"
|
||||||
|
imagePullPolicy: IfNotPresent
|
||||||
|
securityContext:
|
||||||
|
allowPrivilegeEscalation: false
|
||||||
|
capabilities:
|
||||||
|
drop:
|
||||||
|
- ALL
|
||||||
|
readOnlyRootFilesystem: true
|
||||||
|
runAsNonRoot: true
|
||||||
|
runAsUser: 1001
|
||||||
|
args:
|
||||||
|
- --port=7472
|
||||||
|
- --cert-service-name=metallb-webhook-service
|
||||||
|
ports:
|
||||||
|
- name: webhook-server
|
||||||
|
containerPort: 9443
|
||||||
|
- name: metrics
|
||||||
|
containerPort: 7472
|
||||||
|
volumeMounts:
|
||||||
|
- name: cert
|
||||||
|
mountPath: /tmp/k8s-webhook-server/serving-certs
|
||||||
|
readOnly: true
|
||||||
|
livenessProbe:
|
||||||
|
failureThreshold: 3
|
||||||
|
initialDelaySeconds: 10
|
||||||
|
periodSeconds: 10
|
||||||
|
successThreshold: 1
|
||||||
|
timeoutSeconds: 1
|
||||||
|
httpGet:
|
||||||
|
path: /metrics
|
||||||
|
port: metrics
|
||||||
|
readinessProbe:
|
||||||
|
failureThreshold: 3
|
||||||
|
initialDelaySeconds: 10
|
||||||
|
periodSeconds: 10
|
||||||
|
successThreshold: 1
|
||||||
|
timeoutSeconds: 1
|
||||||
|
httpGet:
|
||||||
|
path: /metrics
|
||||||
|
port: metrics
|
||||||
|
resources: {}
|
||||||
|
|
||||||
|
---
|
||||||
|
kind: Service
|
||||||
|
apiVersion: v1
|
||||||
|
metadata:
|
||||||
|
namespace: metallb
|
||||||
|
name: metallb-webhook-service
|
||||||
|
spec:
|
||||||
|
ports:
|
||||||
|
- port: 443
|
||||||
|
targetPort: 9443
|
||||||
|
selector:
|
||||||
|
app: metallb-controller
|
||||||
|
|
||||||
|
---
|
||||||
|
kind: ValidatingWebhookConfiguration
|
||||||
|
apiVersion: admissionregistration.k8s.io/v1
|
||||||
|
metadata:
|
||||||
|
name: metallb-webhook-configuration
|
||||||
|
webhooks:
|
||||||
|
- admissionReviewVersions:
|
||||||
|
- v1
|
||||||
|
clientConfig:
|
||||||
|
service:
|
||||||
|
namespace: metallb
|
||||||
|
name: metallb-webhook-service
|
||||||
|
path: /validate-metallb-io-v1beta1-addresspool
|
||||||
|
failurePolicy: Fail
|
||||||
|
name: addresspoolvalidationwebhook.metallb.io
|
||||||
|
rules:
|
||||||
|
- apiGroups:
|
||||||
|
- metallb.io
|
||||||
|
apiVersions:
|
||||||
|
- v1beta1
|
||||||
|
operations:
|
||||||
|
- CREATE
|
||||||
|
- UPDATE
|
||||||
|
resources:
|
||||||
|
- addresspools
|
||||||
|
sideEffects: None
|
||||||
|
- admissionReviewVersions:
|
||||||
|
- v1
|
||||||
|
clientConfig:
|
||||||
|
service:
|
||||||
|
namespace: metallb
|
||||||
|
name: metallb-webhook-service
|
||||||
|
path: /validate-metallb-io-v1beta2-bgppeer
|
||||||
|
failurePolicy: Fail
|
||||||
|
name: bgppeervalidationwebhook.metallb.io
|
||||||
|
rules:
|
||||||
|
- apiGroups:
|
||||||
|
- metallb.io
|
||||||
|
apiVersions:
|
||||||
|
- v1beta2
|
||||||
|
operations:
|
||||||
|
- CREATE
|
||||||
|
- UPDATE
|
||||||
|
resources:
|
||||||
|
- bgppeers
|
||||||
|
sideEffects: None
|
||||||
|
- admissionReviewVersions:
|
||||||
|
- v1
|
||||||
|
clientConfig:
|
||||||
|
service:
|
||||||
|
namespace: metallb
|
||||||
|
name: metallb-webhook-service
|
||||||
|
path: /validate-metallb-io-v1beta1-ipaddresspool
|
||||||
|
failurePolicy: Fail
|
||||||
|
name: ipaddresspoolvalidationwebhook.metallb.io
|
||||||
|
rules:
|
||||||
|
- apiGroups:
|
||||||
|
- metallb.io
|
||||||
|
apiVersions:
|
||||||
|
- v1beta1
|
||||||
|
operations:
|
||||||
|
- CREATE
|
||||||
|
- UPDATE
|
||||||
|
resources:
|
||||||
|
- ipaddresspools
|
||||||
|
sideEffects: None
|
||||||
|
- admissionReviewVersions:
|
||||||
|
- v1
|
||||||
|
clientConfig:
|
||||||
|
service:
|
||||||
|
namespace: metallb
|
||||||
|
name: metallb-webhook-service
|
||||||
|
path: /validate-metallb-io-v1beta1-bgpadvertisement
|
||||||
|
failurePolicy: Fail
|
||||||
|
name: bgpadvertisementvalidationwebhook.metallb.io
|
||||||
|
rules:
|
||||||
|
- apiGroups:
|
||||||
|
- metallb.io
|
||||||
|
apiVersions:
|
||||||
|
- v1beta1
|
||||||
|
operations:
|
||||||
|
- CREATE
|
||||||
|
- UPDATE
|
||||||
|
resources:
|
||||||
|
- bgpadvertisements
|
||||||
|
sideEffects: None
|
||||||
|
- admissionReviewVersions:
|
||||||
|
- v1
|
||||||
|
clientConfig:
|
||||||
|
service:
|
||||||
|
namespace: metallb
|
||||||
|
name: metallb-webhook-service
|
||||||
|
path: /validate-metallb-io-v1beta1-community
|
||||||
|
failurePolicy: Fail
|
||||||
|
name: communityvalidationwebhook.metallb.io
|
||||||
|
rules:
|
||||||
|
- apiGroups:
|
||||||
|
- metallb.io
|
||||||
|
apiVersions:
|
||||||
|
- v1beta1
|
||||||
|
operations:
|
||||||
|
- CREATE
|
||||||
|
- UPDATE
|
||||||
|
resources:
|
||||||
|
- communities
|
||||||
|
sideEffects: None
|
||||||
|
- admissionReviewVersions:
|
||||||
|
- v1
|
||||||
|
clientConfig:
|
||||||
|
service:
|
||||||
|
namespace: metallb
|
||||||
|
name: metallb-webhook-service
|
||||||
|
path: /validate-metallb-io-v1beta1-bfdprofile
|
||||||
|
failurePolicy: Fail
|
||||||
|
name: bfdprofileyvalidationwebhook.metallb.io
|
||||||
|
rules:
|
||||||
|
- apiGroups:
|
||||||
|
- metallb.io
|
||||||
|
apiVersions:
|
||||||
|
- v1beta1
|
||||||
|
operations:
|
||||||
|
- DELETE
|
||||||
|
resources:
|
||||||
|
- bfdprofiles
|
||||||
|
sideEffects: None
|
||||||
|
- admissionReviewVersions:
|
||||||
|
- v1
|
||||||
|
clientConfig:
|
||||||
|
service:
|
||||||
|
namespace: metallb
|
||||||
|
name: metallb-webhook-service
|
||||||
|
path: /validate-metallb-io-v1beta1-l2advertisement
|
||||||
|
failurePolicy: Fail
|
||||||
|
name: l2advertisementvalidationwebhook.metallb.io
|
||||||
|
rules:
|
||||||
|
- apiGroups:
|
||||||
|
- metallb.io
|
||||||
|
apiVersions:
|
||||||
|
- v1beta1
|
||||||
|
operations:
|
||||||
|
- CREATE
|
||||||
|
- UPDATE
|
||||||
|
resources:
|
||||||
|
- l2advertisements
|
||||||
|
sideEffects: None
|
|
@ -0,0 +1,23 @@
|
||||||
|
---
|
||||||
|
kind: Namespace
|
||||||
|
apiVersion: v1
|
||||||
|
metadata:
|
||||||
|
name: traefik
|
||||||
|
|
||||||
|
---
|
||||||
|
kind: Namespace
|
||||||
|
apiVersion: v1
|
||||||
|
metadata:
|
||||||
|
name: metallb
|
||||||
|
|
||||||
|
---
|
||||||
|
kind: Namespace
|
||||||
|
apiVersion: v1
|
||||||
|
metadata:
|
||||||
|
name: riju
|
||||||
|
|
||||||
|
---
|
||||||
|
kind: Namespace
|
||||||
|
apiVersion: v1
|
||||||
|
metadata:
|
||||||
|
name: riju-user
|
|
@ -0,0 +1,25 @@
|
||||||
|
```bash
|
||||||
|
curl -sSLf https://get.k0s.sh | sudo sh
|
||||||
|
sudo mkdir /etc/k0s
|
||||||
|
k0s config create > /etc/k0s/k0s.yaml
|
||||||
|
```
|
||||||
|
|
||||||
|
Edit to have this config:
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
spec:
|
||||||
|
extensions:
|
||||||
|
storage:
|
||||||
|
type: openebs_local_storage
|
||||||
|
```
|
||||||
|
|
||||||
|
```bash
|
||||||
|
sudo k0s install controller --single
|
||||||
|
sudo k0s start
|
||||||
|
```
|
||||||
|
|
||||||
|
Go to client machine:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
ssh riju-k8s sudo -S k0s kubeconfig admin > ~/.kube/config
|
||||||
|
```
|
|
@ -0,0 +1,102 @@
|
||||||
|
---
|
||||||
|
kind: StatefulSet
|
||||||
|
apiVersion: apps/v1
|
||||||
|
metadata:
|
||||||
|
namespace: riju
|
||||||
|
name: docker-registry
|
||||||
|
spec:
|
||||||
|
replicas: 1
|
||||||
|
serviceName: docker-registry
|
||||||
|
selector:
|
||||||
|
matchLabels:
|
||||||
|
app: docker-registry
|
||||||
|
volumeClaimTemplates:
|
||||||
|
- metadata:
|
||||||
|
name: data
|
||||||
|
spec:
|
||||||
|
accessModes: [ReadWriteOnce]
|
||||||
|
resources:
|
||||||
|
requests:
|
||||||
|
storage: 128Gi
|
||||||
|
storageClassName: openebs-hostpath
|
||||||
|
template:
|
||||||
|
metadata:
|
||||||
|
labels:
|
||||||
|
app: docker-registry
|
||||||
|
spec:
|
||||||
|
volumes:
|
||||||
|
- name: auth
|
||||||
|
secret:
|
||||||
|
secretName: registry-auth
|
||||||
|
containers:
|
||||||
|
- name: registry
|
||||||
|
image: "registry:2"
|
||||||
|
resources: {}
|
||||||
|
readinessProbe:
|
||||||
|
httpGet:
|
||||||
|
path: /
|
||||||
|
port: 5000
|
||||||
|
scheme: HTTP
|
||||||
|
failureThreshold: 1
|
||||||
|
initialDelaySeconds: 2
|
||||||
|
periodSeconds: 10
|
||||||
|
successThreshold: 1
|
||||||
|
timeoutSeconds: 2
|
||||||
|
livenessProbe:
|
||||||
|
httpGet:
|
||||||
|
path: /
|
||||||
|
port: 5000
|
||||||
|
scheme: HTTP
|
||||||
|
failureThreshold: 3
|
||||||
|
initialDelaySeconds: 2
|
||||||
|
periodSeconds: 10
|
||||||
|
successThreshold: 1
|
||||||
|
timeoutSeconds: 2
|
||||||
|
env:
|
||||||
|
- name: REGISTRY_AUTH
|
||||||
|
value: htpasswd
|
||||||
|
- name: REGISTRY_AUTH_HTPASSWD_REALM
|
||||||
|
value: "Registry Realm"
|
||||||
|
- name: REGISTRY_AUTH_HTPASSWD_PATH
|
||||||
|
value: /var/run/registry/auth/htpasswd
|
||||||
|
ports:
|
||||||
|
- name: api
|
||||||
|
containerPort: 5000
|
||||||
|
volumeMounts:
|
||||||
|
- name: auth
|
||||||
|
mountPath: /var/run/registry/auth
|
||||||
|
- name: data
|
||||||
|
mountPath: /var/lib/registry
|
||||||
|
|
||||||
|
---
|
||||||
|
kind: Service
|
||||||
|
apiVersion: v1
|
||||||
|
metadata:
|
||||||
|
namespace: riju
|
||||||
|
name: docker-registry
|
||||||
|
spec:
|
||||||
|
selector:
|
||||||
|
app: docker-registry
|
||||||
|
type: NodePort
|
||||||
|
ports:
|
||||||
|
- name: api
|
||||||
|
port: 80
|
||||||
|
nodePort: 30999
|
||||||
|
targetPort: 5000
|
||||||
|
|
||||||
|
---
|
||||||
|
kind: IngressRoute
|
||||||
|
apiVersion: traefik.containo.us/v1alpha1
|
||||||
|
metadata:
|
||||||
|
namespace: riju
|
||||||
|
name: docker-registry
|
||||||
|
spec:
|
||||||
|
entryPoints:
|
||||||
|
- docker
|
||||||
|
routes:
|
||||||
|
- kind: Rule
|
||||||
|
match: "PathPrefix(`/`)"
|
||||||
|
services:
|
||||||
|
- namespace: riju
|
||||||
|
name: docker-registry
|
||||||
|
port: 80
|
|
@ -0,0 +1,101 @@
|
||||||
|
---
|
||||||
|
kind: StatefulSet
|
||||||
|
apiVersion: apps/v1
|
||||||
|
metadata:
|
||||||
|
namespace: riju
|
||||||
|
name: minio
|
||||||
|
spec:
|
||||||
|
replicas: 1
|
||||||
|
serviceName: minio
|
||||||
|
selector:
|
||||||
|
matchLabels:
|
||||||
|
app: minio
|
||||||
|
volumeClaimTemplates:
|
||||||
|
- metadata:
|
||||||
|
name: data
|
||||||
|
spec:
|
||||||
|
accessModes: [ReadWriteOnce]
|
||||||
|
resources:
|
||||||
|
requests:
|
||||||
|
storage: 16Gi
|
||||||
|
storageClassName: openebs-hostpath
|
||||||
|
template:
|
||||||
|
metadata:
|
||||||
|
labels:
|
||||||
|
app: minio
|
||||||
|
spec:
|
||||||
|
containers:
|
||||||
|
- name: minio
|
||||||
|
image: "minio/minio:RELEASE.2022-12-12T19-27-27Z"
|
||||||
|
resources: {}
|
||||||
|
readinessProbe:
|
||||||
|
httpGet:
|
||||||
|
path: /minio/health/live
|
||||||
|
port: 9000
|
||||||
|
scheme: HTTP
|
||||||
|
failureThreshold: 1
|
||||||
|
initialDelaySeconds: 2
|
||||||
|
periodSeconds: 10
|
||||||
|
successThreshold: 1
|
||||||
|
timeoutSeconds: 2
|
||||||
|
livenessProbe:
|
||||||
|
httpGet:
|
||||||
|
path: /minio/health/live
|
||||||
|
port: 9000
|
||||||
|
scheme: HTTP
|
||||||
|
failureThreshold: 3
|
||||||
|
initialDelaySeconds: 2
|
||||||
|
periodSeconds: 10
|
||||||
|
successThreshold: 1
|
||||||
|
timeoutSeconds: 2
|
||||||
|
args:
|
||||||
|
- "server"
|
||||||
|
- "/data"
|
||||||
|
env:
|
||||||
|
- name: MINIO_ACCESS_KEY
|
||||||
|
valueFrom:
|
||||||
|
secretKeyRef:
|
||||||
|
name: minio-keys
|
||||||
|
key: access-key
|
||||||
|
- name: MINIO_SECRET_KEY
|
||||||
|
valueFrom:
|
||||||
|
secretKeyRef:
|
||||||
|
name: minio-keys
|
||||||
|
key: secret-key
|
||||||
|
ports:
|
||||||
|
- name: api
|
||||||
|
containerPort: 9000
|
||||||
|
volumeMounts:
|
||||||
|
- name: data
|
||||||
|
mountPath: /data
|
||||||
|
|
||||||
|
---
|
||||||
|
kind: Service
|
||||||
|
apiVersion: v1
|
||||||
|
metadata:
|
||||||
|
namespace: riju
|
||||||
|
name: minio
|
||||||
|
spec:
|
||||||
|
selector:
|
||||||
|
app: minio
|
||||||
|
ports:
|
||||||
|
- name: api
|
||||||
|
port: 80
|
||||||
|
targetPort: 9000
|
||||||
|
|
||||||
|
---
|
||||||
|
kind: IngressRoute
|
||||||
|
apiVersion: traefik.containo.us/v1alpha1
|
||||||
|
metadata:
|
||||||
|
namespace: riju
|
||||||
|
name: minio
|
||||||
|
spec:
|
||||||
|
entryPoints:
|
||||||
|
- minio
|
||||||
|
routes:
|
||||||
|
- kind: Rule
|
||||||
|
match: "PathPrefix(`/`)"
|
||||||
|
services:
|
||||||
|
- namespace: riju
|
||||||
|
name: minio
|
||||||
|
port: 80
|
|
@ -0,0 +1,117 @@
|
||||||
|
---
|
||||||
|
kind: ConfigMap
|
||||||
|
apiVersion: v1
|
||||||
|
metadata:
|
||||||
|
namespace: riju
|
||||||
|
name: riju-proxy-config
|
||||||
|
data:
|
||||||
|
default.conf: |
|
||||||
|
underscores_in_headers on;
|
||||||
|
|
||||||
|
server {
|
||||||
|
resolver kube-dns.kube-system.svc.cluster.local;
|
||||||
|
listen 1869 default_server;
|
||||||
|
|
||||||
|
auth_basic "Riju administrative proxy";
|
||||||
|
auth_basic_user_file /etc/nginx/passwd;
|
||||||
|
|
||||||
|
location ~ /(10\.[0-9]+\.[0-9]+\.[0-9]+)/health {
|
||||||
|
proxy_pass http://$1:869/health;
|
||||||
|
}
|
||||||
|
|
||||||
|
location ~ /(10\.[0-9]+\.[0-9]+\.[0-9]+)/exec {
|
||||||
|
proxy_pass http://$1:869/exec$is_args$args;
|
||||||
|
proxy_http_version 1.1;
|
||||||
|
proxy_set_header Upgrade $http_upgrade;
|
||||||
|
proxy_set_header Connection "Upgrade";
|
||||||
|
proxy_set_header Host $host;
|
||||||
|
}
|
||||||
|
|
||||||
|
location / {
|
||||||
|
return 404;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
---
|
||||||
|
kind: Deployment
|
||||||
|
apiVersion: apps/v1
|
||||||
|
metadata:
|
||||||
|
namespace: riju
|
||||||
|
name: riju-proxy
|
||||||
|
spec:
|
||||||
|
replicas: 1
|
||||||
|
selector:
|
||||||
|
matchLabels:
|
||||||
|
app: riju-proxy
|
||||||
|
template:
|
||||||
|
metadata:
|
||||||
|
labels:
|
||||||
|
app: riju-proxy
|
||||||
|
spec:
|
||||||
|
volumes:
|
||||||
|
- name: config
|
||||||
|
configMap:
|
||||||
|
name: riju-proxy-config
|
||||||
|
- name: auth
|
||||||
|
secret:
|
||||||
|
secretName: riju-proxy-auth
|
||||||
|
containers:
|
||||||
|
- name: nginx
|
||||||
|
image: "nginx:1.23"
|
||||||
|
resources: {}
|
||||||
|
readinessProbe:
|
||||||
|
tcpSocket:
|
||||||
|
port: 1869
|
||||||
|
failureThreshold: 1
|
||||||
|
initialDelaySeconds: 2
|
||||||
|
periodSeconds: 10
|
||||||
|
successThreshold: 1
|
||||||
|
timeoutSeconds: 2
|
||||||
|
livenessProbe:
|
||||||
|
tcpSocket:
|
||||||
|
port: 1869
|
||||||
|
failureThreshold: 3
|
||||||
|
initialDelaySeconds: 2
|
||||||
|
periodSeconds: 10
|
||||||
|
successThreshold: 1
|
||||||
|
timeoutSeconds: 2
|
||||||
|
ports:
|
||||||
|
- name: http
|
||||||
|
containerPort: 1869
|
||||||
|
volumeMounts:
|
||||||
|
- name: config
|
||||||
|
mountPath: /etc/nginx/conf.d
|
||||||
|
- name: auth
|
||||||
|
mountPath: /etc/nginx/passwd
|
||||||
|
subPath: htpasswd
|
||||||
|
|
||||||
|
---
|
||||||
|
kind: Service
|
||||||
|
apiVersion: v1
|
||||||
|
metadata:
|
||||||
|
namespace: riju
|
||||||
|
name: riju-proxy
|
||||||
|
spec:
|
||||||
|
selector:
|
||||||
|
app: riju-proxy
|
||||||
|
ports:
|
||||||
|
- name: http
|
||||||
|
port: 1869
|
||||||
|
targetPort: 1869
|
||||||
|
|
||||||
|
---
|
||||||
|
kind: IngressRoute
|
||||||
|
apiVersion: traefik.containo.us/v1alpha1
|
||||||
|
metadata:
|
||||||
|
namespace: riju
|
||||||
|
name: riju-proxy
|
||||||
|
spec:
|
||||||
|
entryPoints:
|
||||||
|
- proxy
|
||||||
|
routes:
|
||||||
|
- kind: Rule
|
||||||
|
match: "PathPrefix(`/`)"
|
||||||
|
services:
|
||||||
|
- namespace: riju
|
||||||
|
name: riju-proxy
|
||||||
|
port: 1869
|
|
@ -0,0 +1,89 @@
|
||||||
|
---
|
||||||
|
kind: Deployment
|
||||||
|
apiVersion: apps/v1
|
||||||
|
metadata:
|
||||||
|
namespace: riju
|
||||||
|
name: riju-server
|
||||||
|
spec:
|
||||||
|
replicas: 1
|
||||||
|
selector:
|
||||||
|
matchLabels:
|
||||||
|
app: riju-server
|
||||||
|
template:
|
||||||
|
metadata:
|
||||||
|
labels:
|
||||||
|
app: riju-server
|
||||||
|
spec:
|
||||||
|
volumes:
|
||||||
|
- name: cache
|
||||||
|
hostPath:
|
||||||
|
path: /var/cache/riju
|
||||||
|
- name: docker
|
||||||
|
hostPath:
|
||||||
|
path: /var/run/docker.sock
|
||||||
|
imagePullSecrets:
|
||||||
|
- name: registry-login
|
||||||
|
containers:
|
||||||
|
- name: server
|
||||||
|
image: "localhost:30999/riju:app"
|
||||||
|
resources: {}
|
||||||
|
readinessProbe:
|
||||||
|
httpGet:
|
||||||
|
path: /
|
||||||
|
port: 6119
|
||||||
|
scheme: HTTP
|
||||||
|
failureThreshold: 1
|
||||||
|
initialDelaySeconds: 2
|
||||||
|
periodSeconds: 10
|
||||||
|
successThreshold: 1
|
||||||
|
timeoutSeconds: 2
|
||||||
|
livenessProbe:
|
||||||
|
httpGet:
|
||||||
|
path: /
|
||||||
|
port: 6119
|
||||||
|
scheme: HTTP
|
||||||
|
failureThreshold: 3
|
||||||
|
initialDelaySeconds: 2
|
||||||
|
periodSeconds: 10
|
||||||
|
successThreshold: 1
|
||||||
|
timeoutSeconds: 2
|
||||||
|
ports:
|
||||||
|
- name: http
|
||||||
|
containerPort: 6119
|
||||||
|
volumeMounts:
|
||||||
|
- name: cache
|
||||||
|
mountPath: /var/cache/riju
|
||||||
|
- name: docker
|
||||||
|
mountPath: /var/run/docker.sock
|
||||||
|
readOnly: true
|
||||||
|
|
||||||
|
---
|
||||||
|
kind: Service
|
||||||
|
apiVersion: v1
|
||||||
|
metadata:
|
||||||
|
namespace: riju
|
||||||
|
name: riju-server
|
||||||
|
spec:
|
||||||
|
selector:
|
||||||
|
app: riju-server
|
||||||
|
ports:
|
||||||
|
- name: http
|
||||||
|
port: 80
|
||||||
|
targetPort: 6119
|
||||||
|
|
||||||
|
---
|
||||||
|
kind: IngressRoute
|
||||||
|
apiVersion: traefik.containo.us/v1alpha1
|
||||||
|
metadata:
|
||||||
|
namespace: riju
|
||||||
|
name: riju-server
|
||||||
|
spec:
|
||||||
|
entryPoints:
|
||||||
|
- https
|
||||||
|
routes:
|
||||||
|
- kind: Rule
|
||||||
|
match: "PathPrefix(`/`)"
|
||||||
|
services:
|
||||||
|
- namespace: riju
|
||||||
|
name: riju-server
|
||||||
|
port: 80
|
|
@ -0,0 +1,95 @@
|
||||||
|
---
|
||||||
|
kind: Secret
|
||||||
|
apiVersion: v1
|
||||||
|
metadata:
|
||||||
|
namespace: metallb
|
||||||
|
name: metallb-memberlist
|
||||||
|
data:
|
||||||
|
secretkey: "{{ .metallb.secretkey | b64enc }}"
|
||||||
|
|
||||||
|
---
|
||||||
|
kind: Secret
|
||||||
|
apiVersion: v1
|
||||||
|
metadata:
|
||||||
|
namespace: riju
|
||||||
|
name: registry-auth
|
||||||
|
data:
|
||||||
|
htpasswd: "{{ .registry.htpasswd | println | b64enc }}"
|
||||||
|
|
||||||
|
---
|
||||||
|
kind: Secret
|
||||||
|
apiVersion: v1
|
||||||
|
metadata:
|
||||||
|
namespace: riju
|
||||||
|
name: registry-login
|
||||||
|
type: kubernetes.io/dockerconfigjson
|
||||||
|
stringData:
|
||||||
|
.dockerconfigjson: |
|
||||||
|
{
|
||||||
|
"auths": {
|
||||||
|
"localhost:30999": {
|
||||||
|
"username": "admin",
|
||||||
|
"password": "{{ .registry.password }}",
|
||||||
|
"auth": "{{ .registry.password | printf "admin:%s" | b64enc }}"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
---
|
||||||
|
kind: Secret
|
||||||
|
apiVersion: v1
|
||||||
|
metadata:
|
||||||
|
namespace: riju-user
|
||||||
|
name: registry-user-login
|
||||||
|
type: kubernetes.io/dockerconfigjson
|
||||||
|
stringData:
|
||||||
|
.dockerconfigjson: |
|
||||||
|
{
|
||||||
|
"auths": {
|
||||||
|
"localhost:30999": {
|
||||||
|
"username": "admin",
|
||||||
|
"password": "{{ .registry.password }}",
|
||||||
|
"auth": "{{ .registry.password | printf "admin:%s" | b64enc }}"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
---
|
||||||
|
kind: Secret
|
||||||
|
apiVersion: v1
|
||||||
|
metadata:
|
||||||
|
namespace: riju
|
||||||
|
name: minio-keys
|
||||||
|
stringData:
|
||||||
|
access-key: "{{ .minio.accessKey }}"
|
||||||
|
secret-key: "{{ .minio.secretKey }}"
|
||||||
|
|
||||||
|
---
|
||||||
|
kind: Secret
|
||||||
|
apiVersion: v1
|
||||||
|
metadata:
|
||||||
|
namespace: riju-user
|
||||||
|
name: minio-user-login
|
||||||
|
stringData:
|
||||||
|
config.json: |
|
||||||
|
{
|
||||||
|
"version": "10",
|
||||||
|
"aliases": {
|
||||||
|
"riju": {
|
||||||
|
"url": "http://minio.riju.svc",
|
||||||
|
"accessKey": "{{ .minio.accessKey }}",
|
||||||
|
"secretKey": "{{ .minio.secretKey }}",
|
||||||
|
"api": "s3v4",
|
||||||
|
"path": "auto"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
---
|
||||||
|
kind: Secret
|
||||||
|
apiVersion: v1
|
||||||
|
metadata:
|
||||||
|
namespace: riju
|
||||||
|
name: riju-proxy-auth
|
||||||
|
data:
|
||||||
|
htpasswd: "{{ .proxy.htpasswd | println | b64enc }}"
|
|
@ -0,0 +1,60 @@
|
||||||
|
---
|
||||||
|
kind: ConfigMap
|
||||||
|
apiVersion: v1
|
||||||
|
metadata:
|
||||||
|
namespace: traefik
|
||||||
|
name: traefik-config
|
||||||
|
data:
|
||||||
|
traefik.yaml: |
|
||||||
|
entryPoints:
|
||||||
|
proxy:
|
||||||
|
address: ":1869"
|
||||||
|
http:
|
||||||
|
tls:
|
||||||
|
certResolver: riju
|
||||||
|
domains:
|
||||||
|
- main: k8s.riju.codes
|
||||||
|
http:
|
||||||
|
address: ":8000"
|
||||||
|
https:
|
||||||
|
address: ":8443"
|
||||||
|
http:
|
||||||
|
tls:
|
||||||
|
certResolver: riju
|
||||||
|
domains:
|
||||||
|
- main: k8s.riju.codes
|
||||||
|
healthcheck:
|
||||||
|
address: ":9000"
|
||||||
|
metrics:
|
||||||
|
address: ":9100"
|
||||||
|
docker:
|
||||||
|
address: ":31000"
|
||||||
|
http:
|
||||||
|
tls:
|
||||||
|
certResolver: riju
|
||||||
|
domains:
|
||||||
|
- main: k8s.riju.codes
|
||||||
|
minio:
|
||||||
|
address: ":32000"
|
||||||
|
http:
|
||||||
|
tls:
|
||||||
|
certResolver: riju
|
||||||
|
domains:
|
||||||
|
- main: k8s.riju.codes
|
||||||
|
ping:
|
||||||
|
entryPoint: "healthcheck"
|
||||||
|
metrics:
|
||||||
|
prometheus:
|
||||||
|
entryPoint: "metrics"
|
||||||
|
providers:
|
||||||
|
kubernetesCRD: {}
|
||||||
|
certificatesResolvers:
|
||||||
|
riju:
|
||||||
|
acme:
|
||||||
|
{{- if not .contact.letsEncryptProductionEnabled }}
|
||||||
|
caServer: https://acme-staging-v02.api.letsencrypt.org/directory
|
||||||
|
{{- end }}
|
||||||
|
email: "{{ .contact.letsEncryptEmail }}"
|
||||||
|
storage: /data/acme.json
|
||||||
|
httpChallenge:
|
||||||
|
entryPoint: http
|
File diff suppressed because it is too large
Load Diff
|
@ -0,0 +1,72 @@
|
||||||
|
# Based on traefik/traefik helm chart 20.8.0 for traefik v2.9.6
|
||||||
|
|
||||||
|
---
|
||||||
|
kind: ServiceAccount
|
||||||
|
apiVersion: v1
|
||||||
|
metadata:
|
||||||
|
namespace: traefik
|
||||||
|
name: traefik
|
||||||
|
|
||||||
|
---
|
||||||
|
kind: ClusterRole
|
||||||
|
apiVersion: rbac.authorization.k8s.io/v1
|
||||||
|
metadata:
|
||||||
|
name: traefik
|
||||||
|
rules:
|
||||||
|
- apiGroups:
|
||||||
|
- extensions
|
||||||
|
- networking.k8s.io
|
||||||
|
resources:
|
||||||
|
- ingressclasses
|
||||||
|
- ingresses
|
||||||
|
verbs:
|
||||||
|
- get
|
||||||
|
- list
|
||||||
|
- watch
|
||||||
|
- apiGroups:
|
||||||
|
- ""
|
||||||
|
resources:
|
||||||
|
- services
|
||||||
|
- endpoints
|
||||||
|
- secrets
|
||||||
|
verbs:
|
||||||
|
- get
|
||||||
|
- list
|
||||||
|
- watch
|
||||||
|
- apiGroups:
|
||||||
|
- extensions
|
||||||
|
- networking.k8s.io
|
||||||
|
resources:
|
||||||
|
- ingresses/status
|
||||||
|
verbs:
|
||||||
|
- update
|
||||||
|
- apiGroups:
|
||||||
|
- traefik.containo.us
|
||||||
|
resources:
|
||||||
|
- ingressroutes
|
||||||
|
- ingressroutetcps
|
||||||
|
- ingressrouteudps
|
||||||
|
- middlewares
|
||||||
|
- middlewaretcps
|
||||||
|
- tlsoptions
|
||||||
|
- tlsstores
|
||||||
|
- traefikservices
|
||||||
|
- serverstransports
|
||||||
|
verbs:
|
||||||
|
- get
|
||||||
|
- list
|
||||||
|
- watch
|
||||||
|
|
||||||
|
---
|
||||||
|
kind: ClusterRoleBinding
|
||||||
|
apiVersion: rbac.authorization.k8s.io/v1
|
||||||
|
metadata:
|
||||||
|
name: traefik
|
||||||
|
roleRef:
|
||||||
|
kind: ClusterRole
|
||||||
|
apiGroup: rbac.authorization.k8s.io
|
||||||
|
name: traefik
|
||||||
|
subjects:
|
||||||
|
- namespace: traefik
|
||||||
|
kind: ServiceAccount
|
||||||
|
name: traefik
|
|
@ -0,0 +1,150 @@
|
||||||
|
# Based on traefik/traefik helm chart 20.8.0 for traefik v2.9.6
|
||||||
|
|
||||||
|
---
|
||||||
|
kind: PersistentVolumeClaim
|
||||||
|
apiVersion: v1
|
||||||
|
metadata:
|
||||||
|
namespace: traefik
|
||||||
|
name: traefik-data
|
||||||
|
spec:
|
||||||
|
accessModes: [ReadWriteOnce]
|
||||||
|
resources:
|
||||||
|
requests:
|
||||||
|
storage: 128Mi
|
||||||
|
storageClassName: openebs-hostpath
|
||||||
|
|
||||||
|
---
|
||||||
|
kind: Deployment
|
||||||
|
apiVersion: apps/v1
|
||||||
|
metadata:
|
||||||
|
namespace: traefik
|
||||||
|
name: traefik
|
||||||
|
spec:
|
||||||
|
replicas: 1
|
||||||
|
selector:
|
||||||
|
matchLabels:
|
||||||
|
app: traefik
|
||||||
|
strategy:
|
||||||
|
rollingUpdate:
|
||||||
|
maxSurge: 1
|
||||||
|
maxUnavailable: 0
|
||||||
|
type: RollingUpdate
|
||||||
|
minReadySeconds: 0
|
||||||
|
template:
|
||||||
|
metadata:
|
||||||
|
annotations:
|
||||||
|
prometheus.io/scrape: "true"
|
||||||
|
prometheus.io/path: "/metrics"
|
||||||
|
prometheus.io/port: "9100"
|
||||||
|
labels:
|
||||||
|
app: traefik
|
||||||
|
spec:
|
||||||
|
serviceAccountName: traefik
|
||||||
|
terminationGracePeriodSeconds: 60
|
||||||
|
hostNetwork: false
|
||||||
|
initContainers:
|
||||||
|
- name: volume-permissions
|
||||||
|
image: busybox:1.35
|
||||||
|
command:
|
||||||
|
- "sh"
|
||||||
|
- "-c"
|
||||||
|
- "touch /data/acme.json && chmod -Rv 600 /data/* && chown 65532:65532 /data/acme.json"
|
||||||
|
volumeMounts:
|
||||||
|
- name: data
|
||||||
|
mountPath: /data
|
||||||
|
containers:
|
||||||
|
- image: traefik:v2.9.6
|
||||||
|
imagePullPolicy: IfNotPresent
|
||||||
|
name: traefik
|
||||||
|
resources: {}
|
||||||
|
readinessProbe:
|
||||||
|
httpGet:
|
||||||
|
path: /ping
|
||||||
|
port: 9000
|
||||||
|
scheme: HTTP
|
||||||
|
failureThreshold: 1
|
||||||
|
initialDelaySeconds: 2
|
||||||
|
periodSeconds: 10
|
||||||
|
successThreshold: 1
|
||||||
|
timeoutSeconds: 2
|
||||||
|
livenessProbe:
|
||||||
|
httpGet:
|
||||||
|
path: /ping
|
||||||
|
port: 9000
|
||||||
|
scheme: HTTP
|
||||||
|
failureThreshold: 3
|
||||||
|
initialDelaySeconds: 2
|
||||||
|
periodSeconds: 10
|
||||||
|
successThreshold: 1
|
||||||
|
timeoutSeconds: 2
|
||||||
|
ports:
|
||||||
|
- name: http
|
||||||
|
containerPort: 8000
|
||||||
|
- name: https
|
||||||
|
containerPort: 8443
|
||||||
|
- name: ping
|
||||||
|
containerPort: 9000
|
||||||
|
- name: docker
|
||||||
|
containerPort: 31000
|
||||||
|
securityContext:
|
||||||
|
capabilities:
|
||||||
|
drop:
|
||||||
|
- ALL
|
||||||
|
readOnlyRootFilesystem: true
|
||||||
|
runAsGroup: 65532
|
||||||
|
runAsNonRoot: true
|
||||||
|
runAsUser: 65532
|
||||||
|
volumeMounts:
|
||||||
|
- name: config
|
||||||
|
mountPath: /etc/traefik
|
||||||
|
- name: data
|
||||||
|
mountPath: /data
|
||||||
|
- name: tmp
|
||||||
|
mountPath: /tmp
|
||||||
|
volumes:
|
||||||
|
- name: config
|
||||||
|
configMap:
|
||||||
|
name: traefik-config
|
||||||
|
- name: data
|
||||||
|
persistentVolumeClaim:
|
||||||
|
claimName: traefik-data
|
||||||
|
- name: tmp
|
||||||
|
emptyDir: {}
|
||||||
|
securityContext:
|
||||||
|
fsGroup: 65532
|
||||||
|
|
||||||
|
---
|
||||||
|
kind: Service
|
||||||
|
apiVersion: v1
|
||||||
|
metadata:
|
||||||
|
namespace: traefik
|
||||||
|
name: traefik
|
||||||
|
annotations:
|
||||||
|
metallb.universe.tf/allow-shared-ip: main
|
||||||
|
spec:
|
||||||
|
type: LoadBalancer
|
||||||
|
selector:
|
||||||
|
app: traefik
|
||||||
|
ports:
|
||||||
|
- port: 80
|
||||||
|
name: http
|
||||||
|
targetPort: 8000
|
||||||
|
- port: 443
|
||||||
|
name: https
|
||||||
|
targetPort: 8443
|
||||||
|
- port: 1869
|
||||||
|
name: proxy
|
||||||
|
- port: 31000
|
||||||
|
name: docker
|
||||||
|
- port: 32000
|
||||||
|
name: minio
|
||||||
|
|
||||||
|
---
|
||||||
|
kind: IngressClass
|
||||||
|
apiVersion: networking.k8s.io/v1
|
||||||
|
metadata:
|
||||||
|
name: traefik
|
||||||
|
annotations:
|
||||||
|
ingressclass.kubernetes.io/is-default-class: "true"
|
||||||
|
spec:
|
||||||
|
controller: traefik.io/ingress-controller
|
|
@ -0,0 +1,44 @@
|
||||||
|
id: "ante"
|
||||||
|
aliases:
|
||||||
|
- "an"
|
||||||
|
name: "Ante"
|
||||||
|
|
||||||
|
install:
|
||||||
|
prepare:
|
||||||
|
apt:
|
||||||
|
- cargo
|
||||||
|
- cmake
|
||||||
|
- libssl-dev
|
||||||
|
- pkg-config
|
||||||
|
- python3-distutils
|
||||||
|
manual: |
|
||||||
|
export PATH="$HOME/.cargo/bin:$PATH"
|
||||||
|
cargo install llvmenv
|
||||||
|
llvmenv init
|
||||||
|
# If compiler is not explicitly set to LLVM, then we get an
|
||||||
|
# error: unrecognized command-line option '-Wnewline-eof'.
|
||||||
|
CC=/usr/bin/clang CXX=/usr/bin/clang++ llvmenv build-entry -G Makefile -j$(nproc) 10.0.1
|
||||||
|
llvmenv global 10.0.1
|
||||||
|
manual: |
|
||||||
|
git clone https://github.com/jfecher/ante.git -n
|
||||||
|
pushd ante
|
||||||
|
git checkout ba940f3b492fb448a6a73b139403eefa7a0daedc
|
||||||
|
LLVM_SYS_100_PREFIX="$(llvmenv prefix)" cargo build --release
|
||||||
|
install -d "${pkg}/opt/ante"
|
||||||
|
install -d "${pkg}/usr/local/bin"
|
||||||
|
cp target/release/ante "${pkg}/usr/local/bin/"
|
||||||
|
cp -R stdlib "${pkg}/opt/ante/"
|
||||||
|
popd
|
||||||
|
|
||||||
|
setup: |
|
||||||
|
mkdir -p "$HOME/.config/ante"
|
||||||
|
cp -R /opt/ante/stdlib "$HOME/.config/ante/"
|
||||||
|
|
||||||
|
main: "main.an"
|
||||||
|
template: |
|
||||||
|
print "Hello, world!"
|
||||||
|
|
||||||
|
compile: |
|
||||||
|
ante main.an
|
||||||
|
run: |
|
||||||
|
./main
|
|
@ -0,0 +1,107 @@
|
||||||
|
id: "claro"
|
||||||
|
name: "Claro"
|
||||||
|
|
||||||
|
info:
|
||||||
|
impl: "Claro"
|
||||||
|
year: 2021
|
||||||
|
desc: "High-level toy programming language providing standardized Software Engineering best practices out of the box"
|
||||||
|
ext:
|
||||||
|
- claro
|
||||||
|
web:
|
||||||
|
home: "https://clarolang.com/"
|
||||||
|
source: "https://github.com/JasonSteving99/claro-lang"
|
||||||
|
category: general
|
||||||
|
mode:
|
||||||
|
- compiled
|
||||||
|
- interpreted
|
||||||
|
platform: []
|
||||||
|
syntax:
|
||||||
|
- c
|
||||||
|
typing: static
|
||||||
|
paradigm:
|
||||||
|
- functional
|
||||||
|
- imperative
|
||||||
|
usage: personal
|
||||||
|
|
||||||
|
install:
|
||||||
|
apt:
|
||||||
|
- default-jdk
|
||||||
|
manual: |
|
||||||
|
install -d "${pkg}/opt/claro/programs"
|
||||||
|
|
||||||
|
# Pull resources from the latest Claro repo release.
|
||||||
|
ver="$(latest_release JasonSteving99/claro-lang | sed 's/^v//')"
|
||||||
|
|
||||||
|
# Pull the tarball of the built Bazel repo's bazel-bin instead of just the sources.
|
||||||
|
wget "https://github.com/JasonSteving99/claro-lang/releases/download/v${ver}/claro-lang-bazel-bin.tar.gz"
|
||||||
|
tar -xf claro-lang-bazel-bin.tar.gz
|
||||||
|
|
||||||
|
#####################################################################################################
|
||||||
|
# We don't want to depend on Bazel at all for rebuilding, it's just one file changing. Rebuild it using
|
||||||
|
# the packaged jar files and then update the jar holding that recompiled file and run the Bazel gen'd
|
||||||
|
# run script which points at all the correct jar runfiles. First though, we need to slightly modify
|
||||||
|
# the Bazel gen'd runscript to rebuild using the packaged jars for us (without explicitly rerunning Bazel
|
||||||
|
# itself since this is super slow and involves starting up a new Bazel server...).
|
||||||
|
pushd claro_programs
|
||||||
|
|
||||||
|
read -r -d '' MY_SCRIPT <<"EOF" ||:
|
||||||
|
REBUILD_CLASSPATH="${CLASSPATH}"
|
||||||
|
# For the purpose of rebuilding, we need lombok and autovalue on the classpath.
|
||||||
|
REBUILD_CLASSPATH+=":lombok-1.18.20.jar"
|
||||||
|
REBUILD_CLASSPATH+=":auto-value-1.5.3.jar"
|
||||||
|
javac -classpath $REBUILD_CLASSPATH Conditions.java
|
||||||
|
# There's an assumption that the dir ./com/claro/ was made in the tarball before this.
|
||||||
|
mv Conditions*.class com/claro
|
||||||
|
jar -uf "${RUNPATH}src/java/com/claro/claro_programs/conditions_compiled_claro_image.jar" com/claro/Conditions*.class
|
||||||
|
java -classpath $CLASSPATH "${ARGS[@]}"
|
||||||
|
EOF
|
||||||
|
|
||||||
|
# Insert MY_SCRIPT into the Bazel run script just before java gets executed. We're reusing Bazel's run
|
||||||
|
# script basically just to get a conveniently curated CLASSPATH variable generated to point to all the
|
||||||
|
# randomly scattered jar files that Bazel places throughout bazel-bin/.
|
||||||
|
sed -i "s|exec \$JAVABIN.*|${MY_SCRIPT//$'\n'/\\n}|" conditions_compiled_claro_image
|
||||||
|
|
||||||
|
chmod -R u+rw *
|
||||||
|
|
||||||
|
popd
|
||||||
|
#####################################################################################################
|
||||||
|
|
||||||
|
cp -R claro_programs/. "${pkg}/opt/claro/programs/"
|
||||||
|
|
||||||
|
wget "https://github.com/JasonSteving99/claro-lang/releases/download/v${ver}/claro_compiler_binary_deploy.jar"
|
||||||
|
cp claro_compiler_binary_deploy.jar "${pkg}/opt/claro/"
|
||||||
|
|
||||||
|
setup: |
|
||||||
|
cp -R /opt/claro/programs "./"
|
||||||
|
|
||||||
|
main: "programs/Conditions.claro"
|
||||||
|
template: |
|
||||||
|
# Thanks for trying out Claro during its early development stages!
|
||||||
|
# To learn Claro by example, check out:
|
||||||
|
# https://clarolang.com/tree/main/src/java/com/claro/claro_programs
|
||||||
|
|
||||||
|
print("Hello, world!");
|
||||||
|
|
||||||
|
repl: |
|
||||||
|
java -jar /opt/claro/claro_compiler_binary_deploy.jar --repl --silent
|
||||||
|
|
||||||
|
# Skip rebuilding the entire compiler all over again and instead just
|
||||||
|
# use the pre-built Claro compiler jar.
|
||||||
|
compile: |
|
||||||
|
java -jar /opt/claro/claro_compiler_binary_deploy.jar \
|
||||||
|
--java_source --silent \
|
||||||
|
--classname=Conditions --package=com.claro \
|
||||||
|
< programs/Conditions.claro \
|
||||||
|
> programs/Conditions.java
|
||||||
|
run: |
|
||||||
|
set -e
|
||||||
|
|
||||||
|
cd programs
|
||||||
|
./conditions_compiled_claro_image ||:
|
||||||
|
|
||||||
|
java -jar /opt/claro/claro_compiler_binary_deploy.jar --repl --silent
|
||||||
|
|
||||||
|
input: |
|
||||||
|
print(123 * 234);
|
||||||
|
|
||||||
|
timeoutFactor: 2
|
|
@ -0,0 +1,22 @@
|
||||||
|
id: "groovy"
|
||||||
|
name: "Groovy"
|
||||||
|
|
||||||
|
install:
|
||||||
|
apt:
|
||||||
|
- groovy
|
||||||
|
|
||||||
|
repl: |
|
||||||
|
JAVA_OPTS="-Djava.util.prefs.systemRoot=$PWD/.java -Djava.util.prefs.userRoot=$PWD/.java/.userPrefs" groovysh
|
||||||
|
|
||||||
|
main: "main.groovy"
|
||||||
|
template: |
|
||||||
|
print "Hello, world!";
|
||||||
|
|
||||||
|
run: |
|
||||||
|
JAVA_OPTS="-Djava.util.prefs.systemRoot=$PWD/.java -Djava.util.prefs.userRoot=$PWD/.java/.userPrefs" groovysh main.groovy
|
||||||
|
|
||||||
|
scope:
|
||||||
|
code: |
|
||||||
|
x = 123 * 234;
|
||||||
|
|
||||||
|
timeoutFactor: 4
|
|
@ -0,0 +1,30 @@
|
||||||
|
id: "ioke"
|
||||||
|
aliases:
|
||||||
|
- "ik"
|
||||||
|
name: "Ioke"
|
||||||
|
|
||||||
|
install:
|
||||||
|
prepare:
|
||||||
|
cert:
|
||||||
|
- "https://letsencrypt.org/certs/lets-encrypt-r3.pem"
|
||||||
|
apt:
|
||||||
|
- default-jdk
|
||||||
|
manual: |
|
||||||
|
install -d "${pkg}/opt/ioke"
|
||||||
|
install -d "${pkg}/usr/local/bin"
|
||||||
|
|
||||||
|
wget https://ioke.org/dist/ioke-ikj-latest.tar.gz -O ioke.tar.gz
|
||||||
|
tar -xf ioke.tar.gz -C "${pkg}/opt/ioke" --strip-components=1
|
||||||
|
ln -s /opt/ioke/bin/ioke "${pkg}/usr/local/bin/ioke"
|
||||||
|
|
||||||
|
repl: |
|
||||||
|
JAVA_OPTS="-Duser.home=$PWD" ioke
|
||||||
|
|
||||||
|
main: "main.ik"
|
||||||
|
template: |
|
||||||
|
"Hello, world!" println
|
||||||
|
|
||||||
|
run: |
|
||||||
|
JAVA_OPTS="-Duser.home=$PWD" ioke main.ik; JAVA_OPTS="-Duser.home=$PWD" ioke
|
||||||
|
|
||||||
|
timeoutFactor: 4
|
|
@ -0,0 +1,29 @@
|
||||||
|
id: "kalyn"
|
||||||
|
name: "Kalyn"
|
||||||
|
|
||||||
|
install:
|
||||||
|
prepare:
|
||||||
|
apt:
|
||||||
|
- haskell-stack
|
||||||
|
manual: |
|
||||||
|
install -d "${pkg}/opt/kalyn"
|
||||||
|
install -d "${pkg}/usr/local/bin"
|
||||||
|
|
||||||
|
git clone https://github.com/radian-software/kalyn.git
|
||||||
|
pushd kalyn
|
||||||
|
stack build kalyn
|
||||||
|
cp "$(stack exec which kalyn)" "${pkg}/usr/local/bin/"
|
||||||
|
cp -R src-kalyn/Stdlib src-kalyn/Stdlib.kalyn "${pkg}/opt/kalyn/"
|
||||||
|
popd
|
||||||
|
|
||||||
|
main: "src-kalyn/Main.kalyn"
|
||||||
|
template: |
|
||||||
|
(import "/opt/kalyn/Stdlib.kalyn")
|
||||||
|
|
||||||
|
(public def main (IO Empty)
|
||||||
|
(print "Hello, world!\n"))
|
||||||
|
|
||||||
|
compile: |
|
||||||
|
kalyn
|
||||||
|
run: |
|
||||||
|
out-kalyn/Main
|
|
@ -0,0 +1,40 @@
|
||||||
|
id: "kotlin"
|
||||||
|
aliases:
|
||||||
|
- "kts"
|
||||||
|
- "kotlinc"
|
||||||
|
name: "Kotlin"
|
||||||
|
monacoLang: kotlin
|
||||||
|
|
||||||
|
install:
|
||||||
|
apt:
|
||||||
|
- default-jre
|
||||||
|
manual: |
|
||||||
|
install -d "${pkg}/opt"
|
||||||
|
install -d "${pkg}/usr/local/bin"
|
||||||
|
install -d "${pkg}/usr/local/lib"
|
||||||
|
|
||||||
|
ver="$(latest_release JetBrains/kotlin)"
|
||||||
|
wget "https://github.com/JetBrains/kotlin/releases/download/${ver}/kotlin-compiler-$(sed 's/^v//' <<< "$ver").zip" -O kotlin.zip
|
||||||
|
unzip kotlin.zip
|
||||||
|
cp -RT kotlinc "${pkg}/opt/kotlin"
|
||||||
|
|
||||||
|
ls "${pkg}/opt/kotlin/bin" | while read name; do
|
||||||
|
ln -s "/opt/kotlin/bin/${name}" "${pkg}/usr/local/bin/"
|
||||||
|
done
|
||||||
|
|
||||||
|
ls "${pkg}/opt/kotlin/lib" | while read name; do
|
||||||
|
ln -s "/opt/kotlin/lib/${name}" "${pkg}/usr/local/lib/"
|
||||||
|
done
|
||||||
|
|
||||||
|
repl: |
|
||||||
|
JAVA_OPTS="-Duser.home=$PWD" kotlinc
|
||||||
|
|
||||||
|
main: "main.kts"
|
||||||
|
template: |
|
||||||
|
println("Hello, world!")
|
||||||
|
|
||||||
|
run: |
|
||||||
|
JAVA_OPTS="-Duser.home=$PWD" kotlinc -script main.kts
|
||||||
|
kotlinc
|
||||||
|
|
||||||
|
timeoutFactor: 4
|
|
@ -41,15 +41,8 @@ install:
|
||||||
- python3
|
- python3
|
||||||
- python3-pip
|
- python3-pip
|
||||||
- black
|
- black
|
||||||
manual: |
|
npm:
|
||||||
install -d "${pkg}/opt/mspyls"
|
- pyright
|
||||||
install -d "${pkg}/usr/local/bin"
|
|
||||||
|
|
||||||
url="$(curl -fsSL "https://pvsc.blob.core.windows.net/python-language-server-stable?restype=container&comp=list&prefix=Python-Language-Server-linux-x64" | grep -Eo 'https://[^<]+\.nupkg' | tail -n1)"
|
|
||||||
wget "${url}"
|
|
||||||
unzip -d "${pkg}/opt/mspyls" Python-Language-Server-linux-x64.*.nupkg
|
|
||||||
chmod +x "${pkg}/opt/mspyls/Microsoft.Python.LanguageServer"
|
|
||||||
ln -s "/opt/mspyls/Microsoft.Python.LanguageServer" "${pkg}/usr/local/bin/Microsoft.Python.LanguageServer"
|
|
||||||
|
|
||||||
repl: |
|
repl: |
|
||||||
python3 -u
|
python3 -u
|
||||||
|
@ -83,10 +76,13 @@ pkg:
|
||||||
|
|
||||||
lsp:
|
lsp:
|
||||||
start: |
|
start: |
|
||||||
Microsoft.Python.LanguageServer
|
pyright-langserver --stdio
|
||||||
init:
|
|
||||||
interpreter:
|
|
||||||
properties:
|
|
||||||
InterpreterPath: /usr/bin/python3
|
|
||||||
code: "import func"
|
code: "import func"
|
||||||
item: "functools"
|
<<<<<<< HEAD
|
||||||
|
item: "functools"
|
||||||
|
=======
|
||||||
|
item: "functools"
|
||||||
|
|
||||||
|
skip:
|
||||||
|
- lsp
|
||||||
|
>>>>>>> d370c5fbc85bf9f479adca7c532ec13c2b54199f
|
||||||
|
|
|
@ -0,0 +1,64 @@
|
||||||
|
id: "qsharp"
|
||||||
|
aliases:
|
||||||
|
- "q"
|
||||||
|
- "qs"
|
||||||
|
name: "Q#"
|
||||||
|
|
||||||
|
install:
|
||||||
|
# Apparently, the Q# project template is hardcoded to use version
|
||||||
|
# 3.x of the .NET SDK. Not sure why.
|
||||||
|
prepare: &install-dotnet
|
||||||
|
preface: |
|
||||||
|
wget "https://packages.microsoft.com/config/ubuntu/${ubuntu_ver}/packages-microsoft-prod.deb"
|
||||||
|
sudo --preserve-env=DEBIAN_FRONTEND apt-get install ./packages-microsoft-prod.deb
|
||||||
|
sudo --preserve-env=DEBIAN_FRONTEND apt-get update
|
||||||
|
apt:
|
||||||
|
- $(grep-aptavail -wF Package "dotnet-sdk-3\.[0-9.]+" -s Package -n | sort -Vr | head -n1)
|
||||||
|
<<: *install-dotnet
|
||||||
|
# We should cache the .dotnet directory to avoid a .NET banner being
|
||||||
|
# printed, and we should cache the main directory because there is a
|
||||||
|
# generated main.csproj file that is needed by .NET. Finally we
|
||||||
|
# should cache the .nuget directory as well as the build artifacts
|
||||||
|
# inside main to avoid a 30s initial compile time.
|
||||||
|
#
|
||||||
|
# We could optimize further but I don't feel like it right now.
|
||||||
|
manual: |
|
||||||
|
install -d "${pkg}/opt/qsharp/skel-home"
|
||||||
|
install -d "${pkg}/opt/qsharp/skel-src"
|
||||||
|
|
||||||
|
dotnet new -i Microsoft.Quantum.ProjectTemplates
|
||||||
|
dotnet new console -lang Q# -o main
|
||||||
|
dotnet run --project main
|
||||||
|
|
||||||
|
shopt -s dotglob
|
||||||
|
cp -R main "${pkg}/opt/qsharp/skel-src/"
|
||||||
|
cp -R "${HOME}/.dotnet" "${HOME}/.nuget" "${pkg}/opt/qsharp/skel-home/"
|
||||||
|
rm "${pkg}/opt/qsharp/skel-src/main/Program.qs"
|
||||||
|
chmod -R a=u,go-w "${pkg}/opt/qsharp"
|
||||||
|
manualInstall: |
|
||||||
|
wget "https://packages.microsoft.com/config/ubuntu/${ubuntu_ver}/packages-microsoft-prod.deb"
|
||||||
|
sudo --preserve-env=DEBIAN_FRONTEND apt-get update
|
||||||
|
sudo --preserve-env=DEBIAN_FRONTEND apt-get install ./packages-microsoft-prod.deb
|
||||||
|
|
||||||
|
setup: |
|
||||||
|
shopt -s dotglob
|
||||||
|
cp -R /opt/qsharp/skel-src/* ./
|
||||||
|
cp -R /opt/qsharp/skel-home/* "${HOME}/"
|
||||||
|
|
||||||
|
main: "main/Main.qs"
|
||||||
|
template: |
|
||||||
|
namespace main {
|
||||||
|
|
||||||
|
open Microsoft.Quantum.Canon;
|
||||||
|
open Microsoft.Quantum.Intrinsic;
|
||||||
|
|
||||||
|
@EntryPoint()
|
||||||
|
operation Main() : Unit {
|
||||||
|
Message("Hello, world!");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
run: |
|
||||||
|
dotnet run --project main
|
||||||
|
|
||||||
|
timeoutFactor: 4
|
|
@ -0,0 +1,36 @@
|
||||||
|
id: "red"
|
||||||
|
name: "Red"
|
||||||
|
|
||||||
|
install:
|
||||||
|
apt:
|
||||||
|
- libcurl4:i386
|
||||||
|
manual: |
|
||||||
|
install -d "${pkg}/opt/red/skel"
|
||||||
|
install -d "${pkg}/usr/local/bin"
|
||||||
|
|
||||||
|
path="$(curl -fsSL https://static.red-lang.org/download.html | grep -Eo '/dl/linux/[^"]+' | head -n1)"
|
||||||
|
wget "https://static.red-lang.org${path}" -O red
|
||||||
|
chmod +x red
|
||||||
|
cp red "${pkg}/usr/local/bin/"
|
||||||
|
|
||||||
|
./red <<< quit
|
||||||
|
cp -R "$HOME/.red" "${pkg}/opt/red/skel/"
|
||||||
|
|
||||||
|
setup: |
|
||||||
|
shopt -s dotglob; cp -R /opt/red/skel/* "${HOME}/"
|
||||||
|
|
||||||
|
# https://github.com/red/red/issues/543#issuecomment-25404212
|
||||||
|
repl: |
|
||||||
|
"$(which red)"
|
||||||
|
input: |
|
||||||
|
DELAY: 5
|
||||||
|
123 * 234
|
||||||
|
|
||||||
|
main: "main.red"
|
||||||
|
template: |
|
||||||
|
Red [Title: "Main"]
|
||||||
|
|
||||||
|
print "Hello, world!"
|
||||||
|
|
||||||
|
run: |
|
||||||
|
"$(which red)" main.red; "$(which red)"
|
|
@ -0,0 +1,25 @@
|
||||||
|
id: "scala"
|
||||||
|
name: "Scala"
|
||||||
|
|
||||||
|
install:
|
||||||
|
apt:
|
||||||
|
- scala
|
||||||
|
|
||||||
|
repl: |
|
||||||
|
scala
|
||||||
|
input: |
|
||||||
|
DELAY: 5
|
||||||
|
123 * 234
|
||||||
|
|
||||||
|
main: "main.scala"
|
||||||
|
template: |
|
||||||
|
println("Hello, world!")
|
||||||
|
|
||||||
|
run: |
|
||||||
|
scala -i main.scala
|
||||||
|
|
||||||
|
scope:
|
||||||
|
code: |
|
||||||
|
val x = 123 * 234
|
||||||
|
|
||||||
|
timeoutFactor: 8
|
|
@ -0,0 +1,73 @@
|
||||||
|
id: "unison"
|
||||||
|
aliases:
|
||||||
|
- "ucm"
|
||||||
|
name: "Unison"
|
||||||
|
|
||||||
|
install:
|
||||||
|
prepare:
|
||||||
|
apt:
|
||||||
|
- haskell-stack
|
||||||
|
manual: |
|
||||||
|
mkdir -p "${pkg}/opt/unison/skel"
|
||||||
|
install -d "${pkg}/usr/local/bin"
|
||||||
|
|
||||||
|
git clone https://github.com/unisonweb/unison.git
|
||||||
|
pushd unison
|
||||||
|
stack build
|
||||||
|
cp "$(stack exec which unison)" "${pkg}/usr/local/bin/"
|
||||||
|
popd
|
||||||
|
|
||||||
|
pushd "${pkg}/opt/unison/skel"
|
||||||
|
"${pkg}/usr/local/bin/unison" -codebase . init
|
||||||
|
LESS="+q" "${pkg}/usr/local/bin/unison" -codebase . <<< 'pull https://github.com/unisonweb/base:.trunk .base'
|
||||||
|
popd
|
||||||
|
|
||||||
|
setup: |
|
||||||
|
shopt -s dotglob
|
||||||
|
cp -R /opt/unison/skel/* ./
|
||||||
|
|
||||||
|
repl: |
|
||||||
|
unison -codebase .
|
||||||
|
input: |
|
||||||
|
DELAY: 10
|
||||||
|
find : [a] -> [a]
|
||||||
|
output: |
|
||||||
|
base.List.reverse
|
||||||
|
|
||||||
|
# runProg implementation courtesy of Robert Offner from Unison Slack!
|
||||||
|
main: "main.u"
|
||||||
|
template: |
|
||||||
|
use io
|
||||||
|
|
||||||
|
runProg: '{IO, Exception} a -> '{IO} ()
|
||||||
|
runProg f = 'let
|
||||||
|
printErr err = match err with
|
||||||
|
Failure _ errMsg _ -> handle putBytes (stdHandle StdErr) (toUtf8 errMsg) with cases
|
||||||
|
{raise _ -> _} -> ()
|
||||||
|
{_} -> ()
|
||||||
|
match catch f with
|
||||||
|
Left err -> printErr err
|
||||||
|
Right _ -> ()
|
||||||
|
|
||||||
|
main: '{IO} ()
|
||||||
|
main = runProg 'let
|
||||||
|
printLine "Hello, world!"
|
||||||
|
createEmpty: ""
|
||||||
|
|
||||||
|
run: |
|
||||||
|
unison -codebase . run.file main.u main
|
||||||
|
echo "Type 'load main.u' at the repl prompt to bring variables into scope."
|
||||||
|
unison -codebase .
|
||||||
|
|
||||||
|
scope:
|
||||||
|
code: |
|
||||||
|
x = 123 * 234
|
||||||
|
input: |
|
||||||
|
DELAY: 15
|
||||||
|
load main.u
|
||||||
|
DELAY: 5
|
||||||
|
add x
|
||||||
|
DELAY: 5
|
||||||
|
display x
|
||||||
|
|
||||||
|
timeoutFactor: 4
|
|
@ -0,0 +1,60 @@
|
||||||
|
id: "v"
|
||||||
|
aliases:
|
||||||
|
- "vlang"
|
||||||
|
name: "V"
|
||||||
|
|
||||||
|
info:
|
||||||
|
year: 2019
|
||||||
|
desc: "Simple, statically-typed compiled programming language designed for building maintainable software"
|
||||||
|
ext:
|
||||||
|
- v
|
||||||
|
web:
|
||||||
|
home: "https://vlang.io/"
|
||||||
|
source: "https://github.com/vlang/v"
|
||||||
|
category: general
|
||||||
|
mode: compiled
|
||||||
|
platform: clr
|
||||||
|
syntax:
|
||||||
|
- c
|
||||||
|
typing:
|
||||||
|
- static
|
||||||
|
paradigm:
|
||||||
|
- functional
|
||||||
|
- imperative
|
||||||
|
usage: personal
|
||||||
|
|
||||||
|
install:
|
||||||
|
manual: |
|
||||||
|
install -d "${pkg}/opt"
|
||||||
|
install -d "${pkg}/usr/local/bin"
|
||||||
|
|
||||||
|
git clone https://github.com/vlang/v.git "${pkg}/opt/v"
|
||||||
|
pushd "${pkg}/opt/v"
|
||||||
|
|
||||||
|
make
|
||||||
|
ln -s /opt/v/v "${pkg}/usr/local/bin/"
|
||||||
|
|
||||||
|
# Force vfmt to get compiled ahead of time, otherwise this will
|
||||||
|
# happen at first invocation and fail due to lack of write
|
||||||
|
# permissions on /opt/v.
|
||||||
|
./v fmt < /dev/null
|
||||||
|
|
||||||
|
popd
|
||||||
|
|
||||||
|
main: "main.v"
|
||||||
|
template: |
|
||||||
|
fn main() {
|
||||||
|
println('Hello, world!')
|
||||||
|
}
|
||||||
|
|
||||||
|
run: |
|
||||||
|
v run main.v
|
||||||
|
|
||||||
|
format:
|
||||||
|
run: |
|
||||||
|
v fmt main.v
|
||||||
|
input: |
|
||||||
|
fn main()
|
||||||
|
{
|
||||||
|
println("Hello, world!")
|
||||||
|
}
|
|
@ -0,0 +1,24 @@
|
||||||
|
id: "verilog"
|
||||||
|
aliases:
|
||||||
|
- "systemverilog"
|
||||||
|
- "iverilog"
|
||||||
|
name: "Verilog"
|
||||||
|
|
||||||
|
install:
|
||||||
|
apt:
|
||||||
|
- iverilog
|
||||||
|
|
||||||
|
main: "main.v"
|
||||||
|
template: |
|
||||||
|
module main;
|
||||||
|
|
||||||
|
initial begin
|
||||||
|
$display("Hello, world!");
|
||||||
|
end
|
||||||
|
|
||||||
|
endmodule
|
||||||
|
|
||||||
|
compile: |
|
||||||
|
iverilog main.v -o main
|
||||||
|
run: |
|
||||||
|
./main
|
12
package.json
12
package.json
|
@ -9,6 +9,7 @@
|
||||||
"@babel/parser": "^7.13.11",
|
"@babel/parser": "^7.13.11",
|
||||||
"@babel/preset-env": "^7.12.11",
|
"@babel/preset-env": "^7.12.11",
|
||||||
"@balena/dockerignore": "^1.0.2",
|
"@balena/dockerignore": "^1.0.2",
|
||||||
|
"@kubernetes/client-node": "^0.18.0",
|
||||||
"@sentry/node": "^6.11.0",
|
"@sentry/node": "^6.11.0",
|
||||||
"async-lock": "^1.2.6",
|
"async-lock": "^1.2.6",
|
||||||
"babel-loader": "^8.2.2",
|
"babel-loader": "^8.2.2",
|
||||||
|
@ -19,7 +20,7 @@
|
||||||
"css-loader": "^5.0.1",
|
"css-loader": "^5.0.1",
|
||||||
"debounce": "^1.2.0",
|
"debounce": "^1.2.0",
|
||||||
"docker-file-parser": "^1.0.5",
|
"docker-file-parser": "^1.0.5",
|
||||||
"ejs": "^3.1.5",
|
"ejs": "^3.1.7",
|
||||||
"express": "^4.17.1",
|
"express": "^4.17.1",
|
||||||
"express-ws": "^4.0.0",
|
"express-ws": "^4.0.0",
|
||||||
"file-loader": "^6.2.0",
|
"file-loader": "^6.2.0",
|
||||||
|
@ -32,9 +33,12 @@
|
||||||
"p-queue": "^6.6.2",
|
"p-queue": "^6.6.2",
|
||||||
"parse-passwd": "^1.0.0",
|
"parse-passwd": "^1.0.0",
|
||||||
"prettier": "^2.3.1",
|
"prettier": "^2.3.1",
|
||||||
|
"prom-client": "^14.0.1",
|
||||||
"regenerator-runtime": "^0.13.7",
|
"regenerator-runtime": "^0.13.7",
|
||||||
|
"semaphore": "^1.1.0",
|
||||||
"strip-ansi": "^6.0.0",
|
"strip-ansi": "^6.0.0",
|
||||||
"style-loader": "^2.0.0",
|
"style-loader": "^2.0.0",
|
||||||
|
"unique-names-generator": "^4.7.1",
|
||||||
"uuid": "^8.3.2",
|
"uuid": "^8.3.2",
|
||||||
"vscode-languageserver-protocol": "3.15.3",
|
"vscode-languageserver-protocol": "3.15.3",
|
||||||
"webpack": "^4.44.2",
|
"webpack": "^4.44.2",
|
||||||
|
@ -42,5 +46,9 @@
|
||||||
"xterm": "^4.9.0",
|
"xterm": "^4.9.0",
|
||||||
"xterm-addon-fit": "^0.4.0",
|
"xterm-addon-fit": "^0.4.0",
|
||||||
"yaml": "^1.10.0"
|
"yaml": "^1.10.0"
|
||||||
}
|
},
|
||||||
|
"$comments": [
|
||||||
|
"limiter version pinned due to https://github.com/jhurliman/node-rate-limiter/issues/80",
|
||||||
|
"monaco-languageclient, monaco-editor, vscode-languageserver-protocol pinned because their APIs changed a bunch and Riju hasn't been updated yet"
|
||||||
|
]
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,48 +0,0 @@
|
||||||
data "amazon-ami" "ubuntu" {
|
|
||||||
filters = {
|
|
||||||
name = "ubuntu/images/hvm-ssd/ubuntu-*-21.04-amd64-server-*"
|
|
||||||
root-device-type = "ebs"
|
|
||||||
virtualization-type = "hvm"
|
|
||||||
}
|
|
||||||
most_recent = true
|
|
||||||
owners = ["099720109477"]
|
|
||||||
}
|
|
||||||
|
|
||||||
locals {
|
|
||||||
timestamp = regex_replace(timestamp(), "[- TZ:]", "")
|
|
||||||
}
|
|
||||||
|
|
||||||
source "amazon-ebs" "ubuntu" {
|
|
||||||
ami_name = "riju-ci-${local.timestamp}"
|
|
||||||
instance_type = "t3.micro"
|
|
||||||
source_ami = "${data.amazon-ami.ubuntu.id}"
|
|
||||||
ssh_username = "ubuntu"
|
|
||||||
|
|
||||||
tag {
|
|
||||||
key = "BillingCategory"
|
|
||||||
value = "Riju"
|
|
||||||
}
|
|
||||||
|
|
||||||
tag {
|
|
||||||
key = "BillingSubcategory"
|
|
||||||
value = "Riju:AMI"
|
|
||||||
}
|
|
||||||
|
|
||||||
tag {
|
|
||||||
key = "Name"
|
|
||||||
value = "riju-ci-${local.timestamp}"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
build {
|
|
||||||
sources = ["source.amazon-ebs.ubuntu"]
|
|
||||||
|
|
||||||
provisioner "file" {
|
|
||||||
destination = "/tmp/riju-init-volume"
|
|
||||||
source = "riju-init-volume"
|
|
||||||
}
|
|
||||||
|
|
||||||
provisioner "shell" {
|
|
||||||
script = "provision-ci.bash"
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,41 +0,0 @@
|
||||||
{
|
|
||||||
"agent": {
|
|
||||||
"metrics_collection_interval": 60,
|
|
||||||
"run_as_user": "root"
|
|
||||||
},
|
|
||||||
"metrics": {
|
|
||||||
"append_dimensions": {
|
|
||||||
"ImageId": "${aws:ImageId}",
|
|
||||||
"InstanceId": "${aws:InstanceId}",
|
|
||||||
"InstanceType": "${aws:InstanceType}"
|
|
||||||
},
|
|
||||||
"aggregation_dimensions": [
|
|
||||||
["RijuInstanceGroup"],
|
|
||||||
["RijuInstanceGroup", "path"]
|
|
||||||
],
|
|
||||||
"metrics_collected": {
|
|
||||||
"cpu": {
|
|
||||||
"append_dimensions": {
|
|
||||||
"RijuInstanceGroup": "Webserver"
|
|
||||||
},
|
|
||||||
"measurement": ["usage_active"],
|
|
||||||
"metrics_collection_interval": 60
|
|
||||||
},
|
|
||||||
"disk": {
|
|
||||||
"append_dimensions": {
|
|
||||||
"RijuInstanceGroup": "Webserver"
|
|
||||||
},
|
|
||||||
"measurement": ["used_percent"],
|
|
||||||
"metrics_collection_interval": 60,
|
|
||||||
"resources": ["/", "/mnt/riju"]
|
|
||||||
},
|
|
||||||
"mem": {
|
|
||||||
"append_dimensions": {
|
|
||||||
"RijuInstanceGroup": "Webserver"
|
|
||||||
},
|
|
||||||
"measurement": ["mem_used_percent"],
|
|
||||||
"metrics_collection_interval": 60
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -0,0 +1,13 @@
|
||||||
|
[Unit]
|
||||||
|
Description=Prometheus node exporter
|
||||||
|
StartLimitBurst=5
|
||||||
|
StartLimitIntervalSec=300
|
||||||
|
|
||||||
|
[Service]
|
||||||
|
Type=exec
|
||||||
|
ExecStart=node_exporter
|
||||||
|
Restart=always
|
||||||
|
RestartSec=5
|
||||||
|
|
||||||
|
[Install]
|
||||||
|
WantedBy=multi-user.target
|
|
@ -0,0 +1,13 @@
|
||||||
|
[Unit]
|
||||||
|
Description=Prometheus
|
||||||
|
StartLimitBurst=5
|
||||||
|
StartLimitIntervalSec=300
|
||||||
|
|
||||||
|
[Service]
|
||||||
|
Type=exec
|
||||||
|
ExecStart=bash -c 'EC2_INSTANCE_ID="$(curl -fsSL http://169.254.169.254/latest/meta-data/instance-id)" prometheus --config.file /etc/prometheus/config.yaml --enable-feature=expand-external-labels'
|
||||||
|
Restart=always
|
||||||
|
RestartSec=5
|
||||||
|
|
||||||
|
[Install]
|
||||||
|
WantedBy=multi-user.target
|
|
@ -0,0 +1,21 @@
|
||||||
|
global:
|
||||||
|
scrape_interval: 15s
|
||||||
|
external_labels:
|
||||||
|
node: "${EC2_INSTANCE_ID}"
|
||||||
|
|
||||||
|
scrape_configs:
|
||||||
|
- job_name: server
|
||||||
|
static_configs:
|
||||||
|
- targets: ["localhost:6121"]
|
||||||
|
- job_name: node
|
||||||
|
static_configs:
|
||||||
|
- targets: ["localhost:9100"]
|
||||||
|
- job_name: prometheus
|
||||||
|
static_configs:
|
||||||
|
- targets: ["localhost:9090"]
|
||||||
|
|
||||||
|
remote_write:
|
||||||
|
- url: "$GRAFANA_PROMETHEUS_HOSTNAME"
|
||||||
|
basic_auth:
|
||||||
|
username: "$GRAFANA_PROMETHEUS_USERNAME"
|
||||||
|
password: "$GRAFANA_API_KEY"
|
|
@ -5,7 +5,7 @@ StartLimitIntervalSec=300
|
||||||
|
|
||||||
[Service]
|
[Service]
|
||||||
Type=exec
|
Type=exec
|
||||||
ExecStart=bash -c 'promtail -config.file /etc/promtail/config.yaml -client.external-labels instance="$(curl -fsSL http://169.254.169.254/latest/meta-data/instance-id)"'
|
ExecStart=bash -c 'promtail -config.file /etc/promtail/config.yaml -client.external-labels node="$(curl -fsSL http://169.254.169.254/latest/meta-data/instance-id)"'
|
||||||
Restart=always
|
Restart=always
|
||||||
RestartSec=5
|
RestartSec=5
|
||||||
|
|
||||||
|
|
|
@ -7,7 +7,7 @@ positions:
|
||||||
filename: /tmp/positions.yaml
|
filename: /tmp/positions.yaml
|
||||||
|
|
||||||
client:
|
client:
|
||||||
url: https://72217:$GRAFANA_API_KEY@logs-prod-us-central1.grafana.net/api/prom/push
|
url: https://$GRAFANA_LOKI_USERNAME:$GRAFANA_API_KEY@$GRAFANA_LOKI_HOSTNAME/api/prom/push
|
||||||
|
|
||||||
scrape_configs:
|
scrape_configs:
|
||||||
- job_name: kernel
|
- job_name: kernel
|
||||||
|
@ -39,6 +39,16 @@ scrape_configs:
|
||||||
regex: "riju\\.service"
|
regex: "riju\\.service"
|
||||||
target_label: source
|
target_label: source
|
||||||
replacement: "supervisor"
|
replacement: "supervisor"
|
||||||
|
- source_labels:
|
||||||
|
- __journal__systemd_unit
|
||||||
|
regex: "prometheus\\.service"
|
||||||
|
target_label: source
|
||||||
|
replacement: "prometheus"
|
||||||
|
- source_labels:
|
||||||
|
- __journal__systemd_unit
|
||||||
|
regex: "node-exporter\\.service"
|
||||||
|
target_label: source
|
||||||
|
replacement: "node-exporter"
|
||||||
- source_labels:
|
- source_labels:
|
||||||
- source
|
- source
|
||||||
regex: "systemd"
|
regex: "systemd"
|
||||||
|
|
|
@ -1,35 +0,0 @@
|
||||||
#!/usr/bin/env bash
|
|
||||||
|
|
||||||
set -euo pipefail
|
|
||||||
|
|
||||||
# I think there is a race condition related to Ubuntu wanting to do an
|
|
||||||
# automated system upgrade at boot, which causes 'apt-get update' to
|
|
||||||
# sometimes fail with an obscure error message.
|
|
||||||
sleep 5
|
|
||||||
|
|
||||||
mkdir /tmp/riju-work
|
|
||||||
pushd /tmp/riju-work
|
|
||||||
|
|
||||||
export DEBIAN_FRONTEND=noninteractive
|
|
||||||
|
|
||||||
sudo -E apt-get update
|
|
||||||
sudo -E apt-get dist-upgrade -y
|
|
||||||
|
|
||||||
sudo -E apt-get install -y curl gnupg lsb-release
|
|
||||||
|
|
||||||
curl -fsSL https://download.docker.com/linux/ubuntu/gpg | sudo -E apt-key add -
|
|
||||||
|
|
||||||
ubuntu_name="$(lsb_release -cs)"
|
|
||||||
|
|
||||||
sudo tee -a /etc/apt/sources.list.d/custom.list >/dev/null <<EOF
|
|
||||||
deb [arch=amd64] https://download.docker.com/linux/ubuntu ${ubuntu_name} stable
|
|
||||||
EOF
|
|
||||||
|
|
||||||
sudo -E apt-get update
|
|
||||||
sudo -E apt-get install -y docker-ce docker-ce-cli containerd.io make
|
|
||||||
|
|
||||||
sudo chown root:root /tmp/riju-init-volume
|
|
||||||
sudo mv /tmp/riju-init-volume /usr/local/bin/
|
|
||||||
|
|
||||||
popd
|
|
||||||
rm -rf /tmp/riju-work
|
|
|
@ -5,6 +5,7 @@ set -euo pipefail
|
||||||
: ${ADMIN_PASSWORD}
|
: ${ADMIN_PASSWORD}
|
||||||
: ${AWS_REGION}
|
: ${AWS_REGION}
|
||||||
: ${S3_BUCKET}
|
: ${S3_BUCKET}
|
||||||
|
: ${S3_CONFIG_PATH}
|
||||||
: ${SUPERVISOR_ACCESS_TOKEN}
|
: ${SUPERVISOR_ACCESS_TOKEN}
|
||||||
|
|
||||||
latest_release() {
|
latest_release() {
|
||||||
|
@ -43,16 +44,12 @@ sudo ./aws/install
|
||||||
|
|
||||||
wget -nv https://s3.us-west-1.amazonaws.com/amazon-ssm-us-west-1/latest/debian_amd64/amazon-ssm-agent.deb
|
wget -nv https://s3.us-west-1.amazonaws.com/amazon-ssm-us-west-1/latest/debian_amd64/amazon-ssm-agent.deb
|
||||||
|
|
||||||
wget -nv https://s3.amazonaws.com/amazoncloudwatch-agent/ubuntu/amd64/latest/amazon-cloudwatch-agent.deb
|
|
||||||
sudo apt-get install -y ./amazon-cloudwatch-agent.deb
|
|
||||||
|
|
||||||
sudo chown root:root \
|
sudo chown root:root \
|
||||||
/tmp/cloudwatch.json /tmp/docker.json /tmp/riju.service \
|
/tmp/docker.json /tmp/riju.service \
|
||||||
/tmp/riju.slice /tmp/riju-init-volume /tmp/riju-supervisor
|
/tmp/riju.slice /tmp/riju-init-volume /tmp/riju-supervisor
|
||||||
|
|
||||||
sudo mv /tmp/docker.json /etc/docker/daemon.json
|
sudo mv /tmp/docker.json /etc/docker/daemon.json
|
||||||
sudo mv /tmp/riju.service /tmp/riju.slice /etc/systemd/system/
|
sudo mv /tmp/riju.service /tmp/riju.slice /etc/systemd/system/
|
||||||
sudo mv /tmp/cloudwatch.json /opt/aws/amazon-cloudwatch-agent/bin/config.json
|
|
||||||
sudo mv /tmp/riju-init-volume /tmp/riju-supervisor /usr/local/bin/
|
sudo mv /tmp/riju-init-volume /tmp/riju-supervisor /usr/local/bin/
|
||||||
|
|
||||||
sudo sed -Ei 's|^#?PermitRootLogin .*|PermitRootLogin no|' /etc/ssh/sshd_config
|
sudo sed -Ei 's|^#?PermitRootLogin .*|PermitRootLogin no|' /etc/ssh/sshd_config
|
||||||
|
@ -61,13 +58,13 @@ sudo sed -Ei 's|^#?PermitEmptyPasswords .*|PermitEmptyPasswords no|' /etc/ssh/ss
|
||||||
sudo sed -Ei "s|\\\$AWS_REGION|${AWS_REGION}|" /etc/systemd/system/riju.service
|
sudo sed -Ei "s|\\\$AWS_REGION|${AWS_REGION}|" /etc/systemd/system/riju.service
|
||||||
sudo sed -Ei "s|\\\$ANALYTICS_TAG|${ANALYTICS_TAG:-}|" /etc/systemd/system/riju.service
|
sudo sed -Ei "s|\\\$ANALYTICS_TAG|${ANALYTICS_TAG:-}|" /etc/systemd/system/riju.service
|
||||||
sudo sed -Ei "s|\\\$S3_BUCKET|${S3_BUCKET}|" /etc/systemd/system/riju.service
|
sudo sed -Ei "s|\\\$S3_BUCKET|${S3_BUCKET}|" /etc/systemd/system/riju.service
|
||||||
|
sudo sed -Ei "s|\\\$S3_CONFIG_PATH|${S3_CONFIG_PATH}|" /etc/systemd/system/riju.service
|
||||||
sudo sed -Ei "s|\\\$SENTRY_DSN|${SENTRY_DSN:-}|" /etc/systemd/system/riju.service
|
sudo sed -Ei "s|\\\$SENTRY_DSN|${SENTRY_DSN:-}|" /etc/systemd/system/riju.service
|
||||||
sudo sed -Ei "s|\\\$SUPERVISOR_ACCESS_TOKEN|${SUPERVISOR_ACCESS_TOKEN}|" /etc/systemd/system/riju.service
|
sudo sed -Ei "s|\\\$SUPERVISOR_ACCESS_TOKEN|${SUPERVISOR_ACCESS_TOKEN}|" /etc/systemd/system/riju.service
|
||||||
|
|
||||||
sudo passwd -l root
|
sudo passwd -l root
|
||||||
sudo useradd admin -g admin -G sudo -s /usr/bin/bash -p "$(echo "${ADMIN_PASSWORD}" | mkpasswd -s)" -m
|
sudo useradd admin -g admin -G sudo -s /usr/bin/bash -p "$(echo "${ADMIN_PASSWORD}" | mkpasswd -s)" -m
|
||||||
|
|
||||||
sudo amazon-cloudwatch-agent-ctl -a fetch-config -m ec2 -s -c file:/opt/aws/amazon-cloudwatch-agent/bin/config.json
|
|
||||||
sudo systemctl enable riju
|
sudo systemctl enable riju
|
||||||
|
|
||||||
if [[ -n "${GRAFANA_API_KEY:-}" ]]; then
|
if [[ -n "${GRAFANA_API_KEY:-}" ]]; then
|
||||||
|
@ -77,16 +74,42 @@ if [[ -n "${GRAFANA_API_KEY:-}" ]]; then
|
||||||
unzip promtail-linux-amd64.zip
|
unzip promtail-linux-amd64.zip
|
||||||
sudo cp promtail-linux-amd64 /usr/local/bin/promtail
|
sudo cp promtail-linux-amd64 /usr/local/bin/promtail
|
||||||
|
|
||||||
sudo chown root:root /tmp/promtail.service /tmp/promtail.yaml
|
ver="$(latest_release prometheus/node_exporter | sed 's/^v//')"
|
||||||
|
|
||||||
sudo mkdir /etc/promtail
|
wget -nv "https://github.com/prometheus/node_exporter/releases/download/v${ver}/node_exporter-${ver}.linux-amd64.tar.gz" -O node_exporter.tar.gz
|
||||||
|
tar -xf node_exporter.tar.gz --strip-components=1
|
||||||
|
sudo cp node_exporter /usr/local/bin/
|
||||||
|
|
||||||
|
ver="$(latest_release prometheus/prometheus | sed 's/^v//')"
|
||||||
|
|
||||||
|
wget -nv "https://github.com/prometheus/prometheus/releases/download/v${ver}/prometheus-${ver}.linux-amd64.tar.gz" -O prometheus.tar.gz
|
||||||
|
tar -xf prometheus.tar.gz --strip-components=1
|
||||||
|
sudo cp prometheus /usr/local/bin/
|
||||||
|
|
||||||
|
sudo chown root:root \
|
||||||
|
/tmp/node-exporter.service /tmp/prometheus.service \
|
||||||
|
/tmp/prometheus.yaml /tmp/promtail.service /tmp/promtail.yaml
|
||||||
|
|
||||||
|
sudo mkdir /etc/prometheus /etc/promtail
|
||||||
|
sudo mv /tmp/prometheus.yaml /etc/prometheus/config.yaml
|
||||||
sudo mv /tmp/promtail.yaml /etc/promtail/config.yaml
|
sudo mv /tmp/promtail.yaml /etc/promtail/config.yaml
|
||||||
sudo mv /tmp/promtail.service /etc/systemd/system/
|
sudo mv /tmp/prometheus.service /tmp/promtail.service /tmp/node-exporter.service \
|
||||||
sudo sed -Ei "s/\\\$GRAFANA_API_KEY/${GRAFANA_API_KEY}/" /etc/promtail/config.yaml
|
/etc/systemd/system/
|
||||||
|
|
||||||
sudo systemctl enable promtail
|
sudo sed -Ei "s/\\\$GRAFANA_API_KEY/${GRAFANA_API_KEY}/" \
|
||||||
|
/etc/prometheus/config.yaml /etc/promtail/config.yaml
|
||||||
|
sudo sed -Ei "s/\\\$GRAFANA_LOKI_HOSTNAME/${GRAFANA_LOKI_HOSTNAME}/" \
|
||||||
|
/etc/promtail/config.yaml
|
||||||
|
sudo sed -Ei "s/\\\$GRAFANA_LOKI_USERNAME/${GRAFANA_LOKI_USERNAME}/" \
|
||||||
|
/etc/promtail/config.yaml
|
||||||
|
sudo sed -Ei "s/\\\$GRAFANA_PROMETHEUS_HOSTNAME/${GRAFANA_PROMETHEUS_HOSTNAME}/" \
|
||||||
|
/etc/prometheus/config.yaml
|
||||||
|
sudo sed -Ei "s/\\\$GRAFANA_PROMETHEUS_USERNAME/${GRAFANA_PROMETHEUS_USERNAME}/" \
|
||||||
|
/etc/prometheus/config.yaml
|
||||||
|
|
||||||
|
sudo systemctl enable node-exporter prometheus promtail
|
||||||
else
|
else
|
||||||
sudo rm /tmp/promtail.yaml /tmp/promtail.service
|
sudo rm /tmp/node-exporter.service /tmp/promtail.yaml /tmp/promtail.service
|
||||||
fi
|
fi
|
||||||
|
|
||||||
sudo userdel ubuntu -f
|
sudo userdel ubuntu -f
|
|
@ -13,6 +13,7 @@ RestartSec=5
|
||||||
Environment=AWS_REGION=$AWS_REGION
|
Environment=AWS_REGION=$AWS_REGION
|
||||||
Environment=ANALYTICS_TAG=$ANALYTICS_TAG
|
Environment=ANALYTICS_TAG=$ANALYTICS_TAG
|
||||||
Environment=S3_BUCKET=$S3_BUCKET
|
Environment=S3_BUCKET=$S3_BUCKET
|
||||||
|
Environment=S3_CONFIG_PATH=$S3_CONFIG_PATH
|
||||||
Environment=SENTRY_DSN=$SENTRY_DSN
|
Environment=SENTRY_DSN=$SENTRY_DSN
|
||||||
Environment=SUPERVISOR_ACCESS_TOKEN=$SUPERVISOR_ACCESS_TOKEN
|
Environment=SUPERVISOR_ACCESS_TOKEN=$SUPERVISOR_ACCESS_TOKEN
|
||||||
|
|
||||||
|
|
|
@ -21,8 +21,3 @@ MemorySwapMax=0
|
||||||
# this space to user code.
|
# this space to user code.
|
||||||
TasksAccounting=true
|
TasksAccounting=true
|
||||||
TasksMax=400000
|
TasksMax=400000
|
||||||
|
|
||||||
# Attempt to deny access to EC2 Instance Metadata service from user
|
|
||||||
# code.
|
|
||||||
IPAccounting=true
|
|
||||||
IPAddressDeny=169.254.169.254
|
|
||||||
|
|
|
@ -13,6 +13,26 @@ variable "analytics_tag" {
|
||||||
default = "${env("ANALYTICS_TAG")}"
|
default = "${env("ANALYTICS_TAG")}"
|
||||||
}
|
}
|
||||||
|
|
||||||
|
variable "grafana_loki_hostname" {
|
||||||
|
type = string
|
||||||
|
default = "${env("GRAFANA_LOKI_HOSTNAME")}"
|
||||||
|
}
|
||||||
|
|
||||||
|
variable "grafana_loki_username" {
|
||||||
|
type = string
|
||||||
|
default = "${env("GRAFANA_LOKI_USERNAME")}"
|
||||||
|
}
|
||||||
|
|
||||||
|
variable "grafana_prometheus_hostname" {
|
||||||
|
type = string
|
||||||
|
default = "${env("GRAFANA_PROMETHEUS_HOSTNAME")}"
|
||||||
|
}
|
||||||
|
|
||||||
|
variable "grafana_prometheus_username" {
|
||||||
|
type = string
|
||||||
|
default = "${env("GRAFANA_PROMETHEUS_USERNAME")}"
|
||||||
|
}
|
||||||
|
|
||||||
variable "grafana_api_key" {
|
variable "grafana_api_key" {
|
||||||
type = string
|
type = string
|
||||||
default = "${env("GRAFANA_API_KEY")}"
|
default = "${env("GRAFANA_API_KEY")}"
|
||||||
|
@ -23,6 +43,11 @@ variable "s3_bucket" {
|
||||||
default = "${env("S3_BUCKET")}"
|
default = "${env("S3_BUCKET")}"
|
||||||
}
|
}
|
||||||
|
|
||||||
|
variable "s3_config_path" {
|
||||||
|
type = string
|
||||||
|
default = "${env("S3_CONFIG_PATH")}"
|
||||||
|
}
|
||||||
|
|
||||||
variable "sentry_dsn" {
|
variable "sentry_dsn" {
|
||||||
type = string
|
type = string
|
||||||
default = "${env("SENTRY_DSN_PACKER")}"
|
default = "${env("SENTRY_DSN_PACKER")}"
|
||||||
|
@ -35,7 +60,8 @@ variable "supervisor_access_token" {
|
||||||
|
|
||||||
data "amazon-ami" "ubuntu" {
|
data "amazon-ami" "ubuntu" {
|
||||||
filters = {
|
filters = {
|
||||||
name = "ubuntu/images/hvm-ssd/ubuntu-*-21.10-amd64-server-*"
|
// EOL: April 2027
|
||||||
|
name = "ubuntu/images/hvm-ssd/ubuntu-*-22.04-amd64-server-*"
|
||||||
root-device-type = "ebs"
|
root-device-type = "ebs"
|
||||||
virtualization-type = "hvm"
|
virtualization-type = "hvm"
|
||||||
}
|
}
|
||||||
|
@ -48,7 +74,7 @@ locals {
|
||||||
}
|
}
|
||||||
|
|
||||||
source "amazon-ebs" "ubuntu" {
|
source "amazon-ebs" "ubuntu" {
|
||||||
ami_name = "riju-web-${local.timestamp}"
|
ami_name = "riju-${local.timestamp}"
|
||||||
instance_type = "t3.small"
|
instance_type = "t3.small"
|
||||||
source_ami = "${data.amazon-ami.ubuntu.id}"
|
source_ami = "${data.amazon-ami.ubuntu.id}"
|
||||||
ssh_username = "ubuntu"
|
ssh_username = "ubuntu"
|
||||||
|
@ -65,7 +91,7 @@ source "amazon-ebs" "ubuntu" {
|
||||||
|
|
||||||
tag {
|
tag {
|
||||||
key = "Name"
|
key = "Name"
|
||||||
value = "riju-web-${local.timestamp}"
|
value = "riju-${local.timestamp}"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -73,13 +99,23 @@ build {
|
||||||
sources = ["source.amazon-ebs.ubuntu"]
|
sources = ["source.amazon-ebs.ubuntu"]
|
||||||
|
|
||||||
provisioner "file" {
|
provisioner "file" {
|
||||||
destination = "/tmp/cloudwatch.json"
|
destination = "/tmp/docker.json"
|
||||||
source = "cloudwatch.json"
|
source = "docker.json"
|
||||||
}
|
}
|
||||||
|
|
||||||
provisioner "file" {
|
provisioner "file" {
|
||||||
destination = "/tmp/docker.json"
|
destination = "/tmp/node-exporter.service"
|
||||||
source = "docker.json"
|
source = "node-exporter.service"
|
||||||
|
}
|
||||||
|
|
||||||
|
provisioner "file" {
|
||||||
|
destination = "/tmp/prometheus.service"
|
||||||
|
source = "prometheus.service"
|
||||||
|
}
|
||||||
|
|
||||||
|
provisioner "file" {
|
||||||
|
destination = "/tmp/prometheus.yaml"
|
||||||
|
source = "prometheus.yaml"
|
||||||
}
|
}
|
||||||
|
|
||||||
provisioner "file" {
|
provisioner "file" {
|
||||||
|
@ -117,11 +153,16 @@ build {
|
||||||
"ADMIN_PASSWORD=${var.admin_password}",
|
"ADMIN_PASSWORD=${var.admin_password}",
|
||||||
"AWS_REGION=${var.aws_region}",
|
"AWS_REGION=${var.aws_region}",
|
||||||
"ANALYTICS_TAG=${var.analytics_tag}",
|
"ANALYTICS_TAG=${var.analytics_tag}",
|
||||||
|
"GRAFANA_LOKI_HOSTNAME=${var.grafana_loki_hostname}",
|
||||||
|
"GRAFANA_LOKI_USERNAME=${var.grafana_loki_username}",
|
||||||
|
"GRAFANA_PROMETHEUS_HOSTNAME=${var.grafana_prometheus_hostname}",
|
||||||
|
"GRAFANA_PROMETHEUS_USERNAME=${var.grafana_prometheus_username}",
|
||||||
"GRAFANA_API_KEY=${var.grafana_api_key}",
|
"GRAFANA_API_KEY=${var.grafana_api_key}",
|
||||||
"S3_BUCKET=${var.s3_bucket}",
|
"S3_BUCKET=${var.s3_bucket}",
|
||||||
|
"S3_CONFIG_PATH=${var.s3_config_path}",
|
||||||
"SENTRY_DSN=${var.sentry_dsn}",
|
"SENTRY_DSN=${var.sentry_dsn}",
|
||||||
"SUPERVISOR_ACCESS_TOKEN=${var.supervisor_access_token}",
|
"SUPERVISOR_ACCESS_TOKEN=${var.supervisor_access_token}",
|
||||||
]
|
]
|
||||||
script = "provision-web.bash"
|
script = "provision.bash"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
module github.com/raxod502/riju/supervisor
|
module github.com/radian-software/riju/supervisor
|
||||||
|
|
||||||
go 1.16
|
go 1.16
|
||||||
|
|
||||||
|
|
|
@ -34,6 +34,9 @@ import (
|
||||||
const bluePort = 6229
|
const bluePort = 6229
|
||||||
const greenPort = 6230
|
const greenPort = 6230
|
||||||
|
|
||||||
|
const blueMetricsPort = 6231
|
||||||
|
const greenMetricsPort = 6232
|
||||||
|
|
||||||
const blueName = "riju-app-blue"
|
const blueName = "riju-app-blue"
|
||||||
const greenName = "riju-app-green"
|
const greenName = "riju-app-green"
|
||||||
|
|
||||||
|
@ -43,8 +46,9 @@ type deploymentConfig struct {
|
||||||
}
|
}
|
||||||
|
|
||||||
type supervisorConfig struct {
|
type supervisorConfig struct {
|
||||||
AccessToken string `env:"SUPERVISOR_ACCESS_TOKEN,notEmpty"`
|
AccessToken string `env:"SUPERVISOR_ACCESS_TOKEN,notEmpty"`
|
||||||
S3Bucket string `env:"S3_BUCKET,notEmpty"`
|
S3Bucket string `env:"S3_BUCKET,notEmpty"`
|
||||||
|
S3ConfigPath string `env:"S3_CONFIG_PATH,notEmpty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type reloadJob struct {
|
type reloadJob struct {
|
||||||
|
@ -56,10 +60,12 @@ type reloadJob struct {
|
||||||
type supervisor struct {
|
type supervisor struct {
|
||||||
config supervisorConfig
|
config supervisorConfig
|
||||||
|
|
||||||
blueProxyHandler http.Handler
|
blueProxyHandler http.Handler
|
||||||
greenProxyHandler http.Handler
|
greenProxyHandler http.Handler
|
||||||
isGreen bool // blue-green deployment
|
blueMetricsProxyHandler http.Handler
|
||||||
deployConfigHash string
|
greenMetricsProxyHandler http.Handler
|
||||||
|
isGreen bool // blue-green deployment
|
||||||
|
deployConfigHash string
|
||||||
|
|
||||||
awsAccountNumber string
|
awsAccountNumber string
|
||||||
awsRegion string
|
awsRegion string
|
||||||
|
@ -102,7 +108,15 @@ func (sv *supervisor) scheduleReload() string {
|
||||||
return uuid
|
return uuid
|
||||||
}
|
}
|
||||||
|
|
||||||
func (sv *supervisor) ServeHTTP(w http.ResponseWriter, r *http.Request) {
|
func (sv *supervisor) serveHTTP(w http.ResponseWriter, r *http.Request, metricsPort bool) {
|
||||||
|
if metricsPort {
|
||||||
|
if sv.isGreen {
|
||||||
|
sv.greenMetricsProxyHandler.ServeHTTP(w, r)
|
||||||
|
} else {
|
||||||
|
sv.blueMetricsProxyHandler.ServeHTTP(w, r)
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
if strings.HasPrefix(r.URL.Path, "/api/supervisor") {
|
if strings.HasPrefix(r.URL.Path, "/api/supervisor") {
|
||||||
authHeader := r.Header.Get("Authorization")
|
authHeader := r.Header.Get("Authorization")
|
||||||
if authHeader == "" {
|
if authHeader == "" {
|
||||||
|
@ -264,12 +278,13 @@ func (sv *supervisor) reload() error {
|
||||||
buf := s3manager.NewWriteAtBuffer([]byte{})
|
buf := s3manager.NewWriteAtBuffer([]byte{})
|
||||||
if _, err := dl.Download(context.Background(), buf, &s3.GetObjectInput{
|
if _, err := dl.Download(context.Background(), buf, &s3.GetObjectInput{
|
||||||
Bucket: &sv.config.S3Bucket,
|
Bucket: &sv.config.S3Bucket,
|
||||||
Key: aws.String("config.json"),
|
Key: aws.String(sv.config.S3ConfigPath),
|
||||||
}); err != nil {
|
}); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
deployCfgBytes := buf.Bytes()
|
||||||
deployCfg := deploymentConfig{}
|
deployCfg := deploymentConfig{}
|
||||||
if err := json.Unmarshal(buf.Bytes(), &deployCfg); err != nil {
|
if err := json.Unmarshal(deployCfgBytes, &deployCfg); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
sv.status("listing locally available images")
|
sv.status("listing locally available images")
|
||||||
|
@ -321,12 +336,8 @@ func (sv *supervisor) reload() error {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
deployCfgStr, err := json.Marshal(&deployCfg)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
h := sha1.New()
|
h := sha1.New()
|
||||||
h.Write([]byte(deployCfgStr))
|
h.Write(deployCfgBytes)
|
||||||
deployCfgHash := fmt.Sprintf("%x", h.Sum(nil))
|
deployCfgHash := fmt.Sprintf("%x", h.Sum(nil))
|
||||||
if deployCfgHash == sv.deployConfigHash {
|
if deployCfgHash == sv.deployConfigHash {
|
||||||
sv.status(fmt.Sprintf("config hash remains at %s", deployCfgHash))
|
sv.status(fmt.Sprintf("config hash remains at %s", deployCfgHash))
|
||||||
|
@ -338,14 +349,17 @@ func (sv *supervisor) reload() error {
|
||||||
))
|
))
|
||||||
}
|
}
|
||||||
var port int
|
var port int
|
||||||
|
var metricsPort int
|
||||||
var name string
|
var name string
|
||||||
var oldName string
|
var oldName string
|
||||||
if sv.isGreen {
|
if sv.isGreen {
|
||||||
port = bluePort
|
port = bluePort
|
||||||
|
metricsPort = blueMetricsPort
|
||||||
name = blueName
|
name = blueName
|
||||||
oldName = greenName
|
oldName = greenName
|
||||||
} else {
|
} else {
|
||||||
port = greenPort
|
port = greenPort
|
||||||
|
metricsPort = greenMetricsPort
|
||||||
name = greenName
|
name = greenName
|
||||||
oldName = blueName
|
oldName = blueName
|
||||||
}
|
}
|
||||||
|
@ -355,6 +369,7 @@ func (sv *supervisor) reload() error {
|
||||||
"-v", "/var/cache/riju:/var/cache/riju",
|
"-v", "/var/cache/riju:/var/cache/riju",
|
||||||
"-v", "/var/run/docker.sock:/var/run/docker.sock",
|
"-v", "/var/run/docker.sock:/var/run/docker.sock",
|
||||||
"-p", fmt.Sprintf("127.0.0.1:%d:6119", port),
|
"-p", fmt.Sprintf("127.0.0.1:%d:6119", port),
|
||||||
|
"-p", fmt.Sprintf("127.0.0.1:%d:6121", metricsPort),
|
||||||
"-e", "ANALYTICS_TAG",
|
"-e", "ANALYTICS_TAG",
|
||||||
"-e", "RIJU_DEPLOY_CONFIG",
|
"-e", "RIJU_DEPLOY_CONFIG",
|
||||||
"-e", "SENTRY_DSN",
|
"-e", "SENTRY_DSN",
|
||||||
|
@ -366,13 +381,13 @@ func (sv *supervisor) reload() error {
|
||||||
)
|
)
|
||||||
dockerRun.Stdout = os.Stdout
|
dockerRun.Stdout = os.Stdout
|
||||||
dockerRun.Stderr = os.Stderr
|
dockerRun.Stderr = os.Stderr
|
||||||
dockerRun.Env = append(os.Environ(), fmt.Sprintf("RIJU_DEPLOY_CONFIG=%s", deployCfgStr))
|
dockerRun.Env = append(os.Environ(), fmt.Sprintf("RIJU_DEPLOY_CONFIG=%s", deployCfgBytes))
|
||||||
if err := dockerRun.Run(); err != nil {
|
if err := dockerRun.Run(); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
sv.status("waiting for container to start up")
|
sv.status("waiting for container to start up")
|
||||||
time.Sleep(5 * time.Second)
|
time.Sleep(5 * time.Second)
|
||||||
sv.status("checking that container is healthy")
|
sv.status("checking that container responds to HTTP")
|
||||||
resp, err := http.Get(fmt.Sprintf("http://localhost:%d", port))
|
resp, err := http.Get(fmt.Sprintf("http://localhost:%d", port))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
|
@ -383,7 +398,25 @@ func (sv *supervisor) reload() error {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
if !strings.Contains(string(body), "python") {
|
if !strings.Contains(string(body), "python") {
|
||||||
return errors.New("container did not appear to be healthy")
|
return errors.New("container did not respond successfully to HTTP")
|
||||||
|
}
|
||||||
|
sv.status("checking that container exposes metrics")
|
||||||
|
resp, err = http.Get(fmt.Sprintf("http://localhost:%d/metrics", metricsPort))
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
defer resp.Body.Close()
|
||||||
|
body, err = io.ReadAll(resp.Body)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if !strings.Contains(string(body), "process_cpu_seconds_total") {
|
||||||
|
return errors.New("container did not expose metrics properly")
|
||||||
|
}
|
||||||
|
if sv.isGreen {
|
||||||
|
sv.status("switching from green to blue")
|
||||||
|
} else {
|
||||||
|
sv.status("switching from blue to green")
|
||||||
}
|
}
|
||||||
sv.isGreen = !sv.isGreen
|
sv.isGreen = !sv.isGreen
|
||||||
sv.status("stopping old container")
|
sv.status("stopping old container")
|
||||||
|
@ -451,8 +484,8 @@ func main() {
|
||||||
}
|
}
|
||||||
|
|
||||||
rijuInitVolume := exec.Command("riju-init-volume")
|
rijuInitVolume := exec.Command("riju-init-volume")
|
||||||
rijuInitVolume.Stdout = rijuInitVolume.Stdout
|
rijuInitVolume.Stdout = os.Stdout
|
||||||
rijuInitVolume.Stderr = rijuInitVolume.Stderr
|
rijuInitVolume.Stderr = os.Stderr
|
||||||
if err := rijuInitVolume.Run(); err != nil {
|
if err := rijuInitVolume.Run(); err != nil {
|
||||||
log.Fatalln(err)
|
log.Fatalln(err)
|
||||||
}
|
}
|
||||||
|
@ -466,6 +499,15 @@ func main() {
|
||||||
log.Fatalln(err)
|
log.Fatalln(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
blueMetricsUrl, err := url.Parse(fmt.Sprintf("http://localhost:%d", blueMetricsPort))
|
||||||
|
if err != nil {
|
||||||
|
log.Fatalln(err)
|
||||||
|
}
|
||||||
|
greenMetricsUrl, err := url.Parse(fmt.Sprintf("http://localhost:%d", greenMetricsPort))
|
||||||
|
if err != nil {
|
||||||
|
log.Fatalln(err)
|
||||||
|
}
|
||||||
|
|
||||||
awsCfg, err := awsConfig.LoadDefaultConfig(context.Background())
|
awsCfg, err := awsConfig.LoadDefaultConfig(context.Background())
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Fatalln(err)
|
log.Fatalln(err)
|
||||||
|
@ -583,18 +625,38 @@ func main() {
|
||||||
}
|
}
|
||||||
|
|
||||||
sv := &supervisor{
|
sv := &supervisor{
|
||||||
config: supervisorCfg,
|
config: supervisorCfg,
|
||||||
blueProxyHandler: httputil.NewSingleHostReverseProxy(blueUrl),
|
blueProxyHandler: httputil.NewSingleHostReverseProxy(blueUrl),
|
||||||
greenProxyHandler: httputil.NewSingleHostReverseProxy(greenUrl),
|
greenProxyHandler: httputil.NewSingleHostReverseProxy(greenUrl),
|
||||||
isGreen: isGreen,
|
blueMetricsProxyHandler: httputil.NewSingleHostReverseProxy(blueMetricsUrl),
|
||||||
deployConfigHash: deployCfgHash,
|
greenMetricsProxyHandler: httputil.NewSingleHostReverseProxy(greenMetricsUrl),
|
||||||
s3: s3.NewFromConfig(awsCfg),
|
isGreen: isGreen,
|
||||||
ecr: ecr.NewFromConfig(awsCfg),
|
deployConfigHash: deployCfgHash,
|
||||||
awsRegion: awsCfg.Region,
|
s3: s3.NewFromConfig(awsCfg),
|
||||||
awsAccountNumber: *ident.Account,
|
ecr: ecr.NewFromConfig(awsCfg),
|
||||||
reloadJobs: map[string]*reloadJob{},
|
awsRegion: awsCfg.Region,
|
||||||
|
awsAccountNumber: *ident.Account,
|
||||||
|
reloadJobs: map[string]*reloadJob{},
|
||||||
}
|
}
|
||||||
go sv.scheduleReload()
|
go sv.scheduleReload()
|
||||||
|
go func() {
|
||||||
|
log.Println("listening on http://127.0.0.1:6121/metrics")
|
||||||
|
log.Fatalln(http.ListenAndServe(
|
||||||
|
"127.0.0.1:6121",
|
||||||
|
http.HandlerFunc(
|
||||||
|
func(w http.ResponseWriter, r *http.Request) {
|
||||||
|
sv.serveHTTP(w, r, true)
|
||||||
|
},
|
||||||
|
),
|
||||||
|
))
|
||||||
|
}()
|
||||||
log.Println("listening on http://0.0.0.0:80")
|
log.Println("listening on http://0.0.0.0:80")
|
||||||
log.Fatalln(http.ListenAndServe("0.0.0.0:80", sv))
|
log.Fatalln(http.ListenAndServe(
|
||||||
|
"0.0.0.0:80",
|
||||||
|
http.HandlerFunc(
|
||||||
|
func(w http.ResponseWriter, r *http.Request) {
|
||||||
|
sv.serveHTTP(w, r, false)
|
||||||
|
},
|
||||||
|
),
|
||||||
|
))
|
||||||
}
|
}
|
||||||
|
|
|
@ -30,6 +30,8 @@ void init() { sentinel_bash[sentinel_bash_len - 1] = '\0'; }
|
||||||
void die_with_usage()
|
void die_with_usage()
|
||||||
{
|
{
|
||||||
die("usage:\n"
|
die("usage:\n"
|
||||||
|
" riju-system-privileged list\n"
|
||||||
|
" riju-system-privileged pull REPO TAG\n"
|
||||||
" riju-system-privileged session UUID LANG [IMAGE-HASH]\n"
|
" riju-system-privileged session UUID LANG [IMAGE-HASH]\n"
|
||||||
" riju-system-privileged exec UUID CMDLINE...\n"
|
" riju-system-privileged exec UUID CMDLINE...\n"
|
||||||
" riju-system-privileged pty UUID CMDLINE...\n"
|
" riju-system-privileged pty UUID CMDLINE...\n"
|
||||||
|
@ -117,6 +119,28 @@ char *parseImageHash(char *imageHash)
|
||||||
return imageHash;
|
return imageHash;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
char *parseRepo(char *repo)
|
||||||
|
{
|
||||||
|
if (strnlen(repo, 501) > 500)
|
||||||
|
die("illegal repo name");
|
||||||
|
for (char *ptr = repo; *ptr; ++ptr)
|
||||||
|
if (!((*ptr >= 'a' && *ptr <= 'z') || (*ptr >= '0' && *ptr <= '9') ||
|
||||||
|
*ptr == '/' || *ptr == '.' || *ptr == '-' || *ptr == '_'))
|
||||||
|
die("illegal repo name");
|
||||||
|
return repo;
|
||||||
|
}
|
||||||
|
|
||||||
|
char *parseTag(char *tag)
|
||||||
|
{
|
||||||
|
if (strnlen(tag, 501) > 500)
|
||||||
|
die("illegal tag name");
|
||||||
|
for (char *ptr = tag; *ptr; ++ptr)
|
||||||
|
if (!((*ptr >= 'a' && *ptr <= 'z') || (*ptr >= '0' && *ptr <= '9') ||
|
||||||
|
*ptr == '.' || *ptr == '-' || *ptr == '_'))
|
||||||
|
die("illegal tag name");
|
||||||
|
return tag;
|
||||||
|
}
|
||||||
|
|
||||||
char *timeout_msg;
|
char *timeout_msg;
|
||||||
|
|
||||||
void sigalrm_die(int signum)
|
void sigalrm_die(int signum)
|
||||||
|
@ -132,7 +156,81 @@ void sigalrm_kill_parent(int signum)
|
||||||
exit(EXIT_FAILURE);
|
exit(EXIT_FAILURE);
|
||||||
}
|
}
|
||||||
|
|
||||||
void session(char *uuid, char *lang, char *imageHash)
|
void cmd_list()
|
||||||
|
{
|
||||||
|
// This command prints a bunch of empty lines because there is no
|
||||||
|
// way to filter to a desired set of images. Caller is expected to
|
||||||
|
// remove empty lines because it's easier in JS than C.
|
||||||
|
char *argv[] = {
|
||||||
|
"docker",
|
||||||
|
"image",
|
||||||
|
"ls",
|
||||||
|
"--format",
|
||||||
|
"{{ if eq .Repository \"riju\" }}{{ .Tag }}{{ end }}",
|
||||||
|
NULL,
|
||||||
|
};
|
||||||
|
execvp(argv[0], argv);
|
||||||
|
die("execvp failed");
|
||||||
|
}
|
||||||
|
|
||||||
|
void cmd_pull(char *repo, char *tag)
|
||||||
|
{
|
||||||
|
char *localImage, *remoteImage;
|
||||||
|
if (asprintf(&remoteImage, "%s:%s", repo, tag) < 0)
|
||||||
|
die("asprintf failed");
|
||||||
|
if (asprintf(&localImage, "riju:%s", tag) < 0)
|
||||||
|
die("asprintf failed");
|
||||||
|
pid_t orig_ppid = getpid();
|
||||||
|
pid_t pid = fork();
|
||||||
|
if (pid < 0)
|
||||||
|
die("fork failed");
|
||||||
|
else if (pid == 0) {
|
||||||
|
if (freopen("/dev/null", "w", stdout) == NULL)
|
||||||
|
die("freopen failed");
|
||||||
|
if (prctl(PR_SET_PDEATHSIG, SIGTERM) < 0)
|
||||||
|
die("prctl failed");
|
||||||
|
if (getppid() != orig_ppid)
|
||||||
|
exit(EXIT_FAILURE);
|
||||||
|
char *argv[] = {
|
||||||
|
"docker", "inspect", "--", localImage, NULL,
|
||||||
|
};
|
||||||
|
execvp(argv[0], argv);
|
||||||
|
die("execvp failed");
|
||||||
|
}
|
||||||
|
siginfo_t info;
|
||||||
|
if (waitid(P_PID, pid, &info, WEXITED) < 0)
|
||||||
|
die("waitid failed");
|
||||||
|
if (info.si_status == 0) {
|
||||||
|
// Image exists already, no need to pull. It is only appropriate
|
||||||
|
// to use cmd_pull with immutable images.
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
orig_ppid = getpid();
|
||||||
|
pid = fork();
|
||||||
|
if (pid < 0)
|
||||||
|
die("fork failed");
|
||||||
|
else if (pid == 0) {
|
||||||
|
if (prctl(PR_SET_PDEATHSIG, SIGTERM) < 0)
|
||||||
|
die("prctl failed");
|
||||||
|
if (getppid() != orig_ppid)
|
||||||
|
exit(EXIT_FAILURE);
|
||||||
|
char *argv[] = {
|
||||||
|
"docker", "pull", "--", remoteImage, NULL,
|
||||||
|
};
|
||||||
|
execvp(argv[0], argv);
|
||||||
|
}
|
||||||
|
if (waitid(P_PID, pid, &info, WEXITED) < 0)
|
||||||
|
die("waitid failed");
|
||||||
|
if (info.si_status != 0)
|
||||||
|
die("child process failed");
|
||||||
|
char *argv[] = {
|
||||||
|
"docker", "tag", "--", remoteImage, localImage,
|
||||||
|
};
|
||||||
|
execvp(argv[0], argv);
|
||||||
|
die("execvp failed");
|
||||||
|
}
|
||||||
|
|
||||||
|
void cmd_session(char *uuid, char *lang, char *imageHash)
|
||||||
{
|
{
|
||||||
if (setvbuf(stdout, NULL, _IONBF, 0) != 0)
|
if (setvbuf(stdout, NULL, _IONBF, 0) != 0)
|
||||||
die("setvbuf failed");
|
die("setvbuf failed");
|
||||||
|
@ -240,6 +338,13 @@ void session(char *uuid, char *lang, char *imageHash)
|
||||||
"4000",
|
"4000",
|
||||||
"--cgroup-parent",
|
"--cgroup-parent",
|
||||||
"riju.slice",
|
"riju.slice",
|
||||||
|
// Deny access to outside networking for now in order to limit
|
||||||
|
// abuse, as we've received abuse reports from AWS. We should
|
||||||
|
// be able to remove this (and indeed we'll *want* to, in
|
||||||
|
// order to support package installation) by replacing it with
|
||||||
|
// a more fine-grained network control such as limiting
|
||||||
|
// outbound bandwidth.
|
||||||
|
"--network=none",
|
||||||
"--label",
|
"--label",
|
||||||
"riju.category=user-session",
|
"riju.category=user-session",
|
||||||
"--label",
|
"--label",
|
||||||
|
@ -250,6 +355,7 @@ void session(char *uuid, char *lang, char *imageHash)
|
||||||
(char *)sentinel_bash,
|
(char *)sentinel_bash,
|
||||||
NULL,
|
NULL,
|
||||||
};
|
};
|
||||||
|
|
||||||
execvp(argv[0], argv);
|
execvp(argv[0], argv);
|
||||||
die("execvp failed");
|
die("execvp failed");
|
||||||
}
|
}
|
||||||
|
@ -287,7 +393,7 @@ void session(char *uuid, char *lang, char *imageHash)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void exec(char *uuid, int argc, char **cmdline, bool pty)
|
void cmd_exec(char *uuid, int argc, char **cmdline, bool pty)
|
||||||
{
|
{
|
||||||
if (setvbuf(stdout, NULL, _IONBF, 0) != 0)
|
if (setvbuf(stdout, NULL, _IONBF, 0) != 0)
|
||||||
die("setvbuf failed");
|
die("setvbuf failed");
|
||||||
|
@ -455,7 +561,7 @@ void exec(char *uuid, int argc, char **cmdline, bool pty)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void teardown(char *uuid)
|
void cmd_teardown(char *uuid)
|
||||||
{
|
{
|
||||||
if (setuid(0) != 0)
|
if (setuid(0) != 0)
|
||||||
die("setuid failed");
|
die("setuid failed");
|
||||||
|
@ -483,31 +589,45 @@ int main(int argc, char **argv)
|
||||||
die("seteuid failed");
|
die("seteuid failed");
|
||||||
if (argc < 2)
|
if (argc < 2)
|
||||||
die_with_usage();
|
die_with_usage();
|
||||||
|
if (!strcmp(argv[1], "list")) {
|
||||||
|
if (argc != 2)
|
||||||
|
die_with_usage();
|
||||||
|
cmd_list();
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
if (!strcmp(argv[1], "pull")) {
|
||||||
|
if (argc != 4)
|
||||||
|
die_with_usage();
|
||||||
|
char *repo = parseRepo(argv[2]);
|
||||||
|
char *tag = parseTag(argv[3]);
|
||||||
|
cmd_pull(repo, tag);
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
if (!strcmp(argv[1], "session")) {
|
if (!strcmp(argv[1], "session")) {
|
||||||
if (argc < 4 || argc > 5)
|
if (argc < 4 || argc > 5)
|
||||||
die_with_usage();
|
die_with_usage();
|
||||||
char *uuid = parseUUID(argv[2]);
|
char *uuid = parseUUID(argv[2]);
|
||||||
char *lang = parseLang(argv[3]);
|
char *lang = parseLang(argv[3]);
|
||||||
char *imageHash = argc == 5 ? parseImageHash(argv[4]) : NULL;
|
char *imageHash = argc == 5 ? parseImageHash(argv[4]) : NULL;
|
||||||
session(uuid, lang, imageHash);
|
cmd_session(uuid, lang, imageHash);
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
if (!strcmp(argv[1], "exec")) {
|
if (!strcmp(argv[1], "exec")) {
|
||||||
if (argc < 4)
|
if (argc < 4)
|
||||||
die_with_usage();
|
die_with_usage();
|
||||||
exec(parseUUID(argv[2]), argc - 3, &argv[3], false);
|
cmd_exec(parseUUID(argv[2]), argc - 3, &argv[3], false);
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
if (!strcmp(argv[1], "pty")) {
|
if (!strcmp(argv[1], "pty")) {
|
||||||
if (argc < 4)
|
if (argc < 4)
|
||||||
die_with_usage();
|
die_with_usage();
|
||||||
exec(parseUUID(argv[2]), argc - 3, &argv[3], true);
|
cmd_exec(parseUUID(argv[2]), argc - 3, &argv[3], true);
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
if (!strcmp(argv[1], "teardown")) {
|
if (!strcmp(argv[1], "teardown")) {
|
||||||
if (argc < 2)
|
if (argc < 2)
|
||||||
die_with_usage();
|
die_with_usage();
|
||||||
teardown(argc >= 3 ? parseUUID(argv[2]) : NULL);
|
cmd_teardown(argc >= 3 ? parseUUID(argv[2]) : NULL);
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
die_with_usage();
|
die_with_usage();
|
||||||
|
|
|
@ -6,12 +6,3 @@ data "aws_ami" "server" {
|
||||||
values = [data.external.env.result.AMI_NAME]
|
values = [data.external.env.result.AMI_NAME]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
# data "aws_ami" "ci" {
|
|
||||||
# owners = ["self"]
|
|
||||||
|
|
||||||
# filter {
|
|
||||||
# name = "name"
|
|
||||||
# values = [data.external.env.result.CI_AMI_NAME]
|
|
||||||
# }
|
|
||||||
# }
|
|
||||||
|
|
174
tf/cloudwatch.tf
174
tf/cloudwatch.tf
|
@ -1,174 +0,0 @@
|
||||||
resource "aws_cloudwatch_metric_alarm" "server_cpu" {
|
|
||||||
alarm_name = "riju-server-cpu-high"
|
|
||||||
comparison_operator = "GreaterThanOrEqualToThreshold"
|
|
||||||
evaluation_periods = "30"
|
|
||||||
datapoints_to_alarm = "15"
|
|
||||||
metric_name = "cpu_usage_active"
|
|
||||||
namespace = "CWAgent"
|
|
||||||
period = "60"
|
|
||||||
statistic = "Average"
|
|
||||||
threshold = "70"
|
|
||||||
alarm_description = "Average CPU usage on Riju server is above 70% for 30 minutes"
|
|
||||||
ok_actions = [aws_sns_topic.riju.arn]
|
|
||||||
alarm_actions = [aws_sns_topic.riju.arn]
|
|
||||||
insufficient_data_actions = [aws_sns_topic.riju.arn]
|
|
||||||
dimensions = {
|
|
||||||
RijuInstanceGroup = "Webserver"
|
|
||||||
}
|
|
||||||
|
|
||||||
tags = {
|
|
||||||
BillingSubcategory = "Riju:CloudWatch:Alarm"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
resource "aws_cloudwatch_metric_alarm" "server_memory" {
|
|
||||||
alarm_name = "riju-server-memory-high"
|
|
||||||
comparison_operator = "GreaterThanOrEqualToThreshold"
|
|
||||||
evaluation_periods = "30"
|
|
||||||
datapoints_to_alarm = "15"
|
|
||||||
metric_name = "mem_used_percent"
|
|
||||||
namespace = "CWAgent"
|
|
||||||
period = "60"
|
|
||||||
statistic = "Average"
|
|
||||||
threshold = "70"
|
|
||||||
alarm_description = "Average memory usage on Riju server is above 70% for 30 minutes"
|
|
||||||
ok_actions = [aws_sns_topic.riju.arn]
|
|
||||||
alarm_actions = [aws_sns_topic.riju.arn]
|
|
||||||
insufficient_data_actions = [aws_sns_topic.riju.arn]
|
|
||||||
dimensions = {
|
|
||||||
RijuInstanceGroup = "Webserver"
|
|
||||||
}
|
|
||||||
|
|
||||||
tags = {
|
|
||||||
BillingSubcategory = "Riju:CloudWatch:Alarm"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
resource "aws_cloudwatch_metric_alarm" "server_data_volume_disk_space" {
|
|
||||||
alarm_name = "riju-server-data-volume-disk-usage-high"
|
|
||||||
comparison_operator = "GreaterThanOrEqualToThreshold"
|
|
||||||
evaluation_periods = "5"
|
|
||||||
datapoints_to_alarm = "5"
|
|
||||||
metric_name = "disk_used_percent"
|
|
||||||
namespace = "CWAgent"
|
|
||||||
period = "60"
|
|
||||||
statistic = "Average"
|
|
||||||
threshold = "70"
|
|
||||||
alarm_description = "Disk space usage for data volume on Riju server is above 70%"
|
|
||||||
ok_actions = [aws_sns_topic.riju.arn]
|
|
||||||
alarm_actions = [aws_sns_topic.riju.arn]
|
|
||||||
insufficient_data_actions = [aws_sns_topic.riju.arn]
|
|
||||||
dimensions = {
|
|
||||||
RijuInstanceGroup = "Webserver"
|
|
||||||
path = "/mnt/riju"
|
|
||||||
}
|
|
||||||
|
|
||||||
tags = {
|
|
||||||
BillingSubcategory = "Riju:CloudWatch:Alarm"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
resource "aws_cloudwatch_metric_alarm" "server_root_volume_disk_space" {
|
|
||||||
alarm_name = "riju-server-root-volume-disk-usage-high"
|
|
||||||
comparison_operator = "GreaterThanOrEqualToThreshold"
|
|
||||||
evaluation_periods = "5"
|
|
||||||
datapoints_to_alarm = "5"
|
|
||||||
metric_name = "disk_used_percent"
|
|
||||||
namespace = "CWAgent"
|
|
||||||
period = "60"
|
|
||||||
statistic = "Average"
|
|
||||||
threshold = "70"
|
|
||||||
alarm_description = "Disk space usage for root volume on Riju server is above 70%"
|
|
||||||
ok_actions = [aws_sns_topic.riju.arn]
|
|
||||||
alarm_actions = [aws_sns_topic.riju.arn]
|
|
||||||
insufficient_data_actions = [aws_sns_topic.riju.arn]
|
|
||||||
dimensions = {
|
|
||||||
RijuInstanceGroup = "Webserver"
|
|
||||||
path = "/"
|
|
||||||
}
|
|
||||||
|
|
||||||
tags = {
|
|
||||||
BillingSubcategory = "Riju:CloudWatch:Alarm"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
resource "aws_cloudwatch_dashboard" "riju" {
|
|
||||||
dashboard_name = "Riju"
|
|
||||||
dashboard_body = <<EOF
|
|
||||||
{
|
|
||||||
"widgets": [
|
|
||||||
{
|
|
||||||
"type": "metric",
|
|
||||||
"x": 0,
|
|
||||||
"y": 0,
|
|
||||||
"width": 6,
|
|
||||||
"height": 6,
|
|
||||||
"properties": {
|
|
||||||
"title": "CPU",
|
|
||||||
"annotations": {
|
|
||||||
"alarms": [
|
|
||||||
"${aws_cloudwatch_metric_alarm.server_cpu.arn}"
|
|
||||||
]
|
|
||||||
},
|
|
||||||
"view": "timeSeries",
|
|
||||||
"stacked": false
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"type": "metric",
|
|
||||||
"x": 12,
|
|
||||||
"y": 0,
|
|
||||||
"width": 6,
|
|
||||||
"height": 6,
|
|
||||||
"properties": {
|
|
||||||
"title": "Root volume disk space",
|
|
||||||
"annotations": {
|
|
||||||
"alarms": [
|
|
||||||
"${aws_cloudwatch_metric_alarm.server_root_volume_disk_space.arn}"
|
|
||||||
]
|
|
||||||
},
|
|
||||||
"view": "timeSeries",
|
|
||||||
"stacked": false,
|
|
||||||
"type": "chart"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"type": "metric",
|
|
||||||
"x": 18,
|
|
||||||
"y": 0,
|
|
||||||
"width": 6,
|
|
||||||
"height": 6,
|
|
||||||
"properties": {
|
|
||||||
"title": "Data volume disk space",
|
|
||||||
"annotations": {
|
|
||||||
"alarms": [
|
|
||||||
"${aws_cloudwatch_metric_alarm.server_data_volume_disk_space.arn}"
|
|
||||||
]
|
|
||||||
},
|
|
||||||
"view": "timeSeries",
|
|
||||||
"stacked": false,
|
|
||||||
"type": "chart"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"type": "metric",
|
|
||||||
"x": 6,
|
|
||||||
"y": 0,
|
|
||||||
"width": 6,
|
|
||||||
"height": 6,
|
|
||||||
"properties": {
|
|
||||||
"title": "Memory",
|
|
||||||
"annotations": {
|
|
||||||
"alarms": [
|
|
||||||
"${aws_cloudwatch_metric_alarm.server_memory.arn}"
|
|
||||||
]
|
|
||||||
},
|
|
||||||
"view": "timeSeries",
|
|
||||||
"stacked": false,
|
|
||||||
"type": "chart"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
EOF
|
|
||||||
}
|
|
|
@ -50,7 +50,7 @@ resource "aws_launch_template" "server" {
|
||||||
device_name = "/dev/sdh"
|
device_name = "/dev/sdh"
|
||||||
ebs {
|
ebs {
|
||||||
volume_type = "gp3"
|
volume_type = "gp3"
|
||||||
volume_size = 256
|
volume_size = 128
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
11
tf/iam.tf
11
tf/iam.tf
|
@ -1,7 +1,3 @@
|
||||||
data "aws_iam_policy" "cloudwatch" {
|
|
||||||
name = "CloudWatchAgentServerPolicy"
|
|
||||||
}
|
|
||||||
|
|
||||||
data "aws_iam_policy" "ssm" {
|
data "aws_iam_policy" "ssm" {
|
||||||
name = "AmazonSSMManagedInstanceCore"
|
name = "AmazonSSMManagedInstanceCore"
|
||||||
}
|
}
|
||||||
|
@ -194,11 +190,6 @@ resource "aws_iam_role_policy_attachment" "server" {
|
||||||
policy_arn = aws_iam_policy.server.arn
|
policy_arn = aws_iam_policy.server.arn
|
||||||
}
|
}
|
||||||
|
|
||||||
resource "aws_iam_role_policy_attachment" "server_cloudwatch" {
|
|
||||||
role = aws_iam_role.server.name
|
|
||||||
policy_arn = data.aws_iam_policy.cloudwatch.arn
|
|
||||||
}
|
|
||||||
|
|
||||||
resource "aws_iam_role_policy_attachment" "server_ssm" {
|
resource "aws_iam_role_policy_attachment" "server_ssm" {
|
||||||
role = aws_iam_role.server.name
|
role = aws_iam_role.server.name
|
||||||
policy_arn = data.aws_iam_policy.ssm.arn
|
policy_arn = data.aws_iam_policy.ssm.arn
|
||||||
|
@ -295,4 +286,4 @@ resource "aws_iam_user_policy_attachment" "grafana_cloudwatch" {
|
||||||
|
|
||||||
resource "aws_iam_access_key" "grafana" {
|
resource "aws_iam_access_key" "grafana" {
|
||||||
user = aws_iam_user.grafana.name
|
user = aws_iam_user.grafana.name
|
||||||
}
|
}
|
||||||
|
|
|
@ -10,12 +10,3 @@ output "deploy_aws_secret_access_key" {
|
||||||
value = aws_iam_access_key.deploy.secret
|
value = aws_iam_access_key.deploy.secret
|
||||||
sensitive = true
|
sensitive = true
|
||||||
}
|
}
|
||||||
|
|
||||||
output "grafana_aws_access_key_id" {
|
|
||||||
value = aws_iam_access_key.grafana.id
|
|
||||||
}
|
|
||||||
|
|
||||||
output "grafana_aws_secret_access_key" {
|
|
||||||
value = aws_iam_access_key.grafana.secret
|
|
||||||
sensitive = true
|
|
||||||
}
|
|
||||||
|
|
31
tf/ssm.tf
31
tf/ssm.tf
|
@ -1,31 +0,0 @@
|
||||||
resource "aws_ssm_parameter" "web_ami_id" {
|
|
||||||
name = "riju-web-ami-id"
|
|
||||||
type = "String"
|
|
||||||
value = data.aws_ami.server.id
|
|
||||||
data_type = "aws:ec2:image"
|
|
||||||
}
|
|
||||||
|
|
||||||
# resource "aws_ssm_parameter" "ci_ami_id" {
|
|
||||||
# name = "riju-ci-ami-id"
|
|
||||||
# type = "String"
|
|
||||||
# value = data.aws_ami.ci.id
|
|
||||||
# data_type = "aws:ec2:image"
|
|
||||||
# }
|
|
||||||
|
|
||||||
resource "aws_ssm_parameter" "docker_repo" {
|
|
||||||
name = "riju-docker-repo-host"
|
|
||||||
type = "String"
|
|
||||||
value = aws_ecr_repository.riju.repository_url
|
|
||||||
}
|
|
||||||
|
|
||||||
resource "aws_ssm_parameter" "public_docker_repo" {
|
|
||||||
name = "riju-public-docker-repo-host"
|
|
||||||
type = "String"
|
|
||||||
value = aws_ecrpublic_repository.riju.repository_uri
|
|
||||||
}
|
|
||||||
|
|
||||||
resource "aws_ssm_parameter" "s3_bucket" {
|
|
||||||
name = "riju-s3-bucket-name"
|
|
||||||
type = "String"
|
|
||||||
value = aws_s3_bucket.riju.bucket
|
|
||||||
}
|
|
|
@ -1,15 +1,10 @@
|
||||||
import crypto from "crypto";
|
|
||||||
import { promises as fs } from "fs";
|
|
||||||
import http from "http";
|
import http from "http";
|
||||||
import url from "url";
|
import url from "url";
|
||||||
|
|
||||||
import { Command } from "commander";
|
import { Command } from "commander";
|
||||||
import express from "express";
|
import express from "express";
|
||||||
|
|
||||||
import { getSharedDepsForLangConfig, readLangConfig } from "../lib/yaml.js";
|
import { runCommand } from "./util.js";
|
||||||
import { getLocalImageLabel } from "./docker-util.js";
|
|
||||||
import { hashDockerfile } from "./hash-dockerfile.js";
|
|
||||||
import { getDebHash, runCommand } from "./util.js";
|
|
||||||
|
|
||||||
// Get a Node.js http server object that will allow the Docker
|
// Get a Node.js http server object that will allow the Docker
|
||||||
// build to fetch files from outside the container, without them
|
// build to fetch files from outside the container, without them
|
||||||
|
@ -27,44 +22,6 @@ async function main() {
|
||||||
program.option("--debug", "interactive debugging");
|
program.option("--debug", "interactive debugging");
|
||||||
program.parse(process.argv);
|
program.parse(process.argv);
|
||||||
const { lang, debug } = program.opts();
|
const { lang, debug } = program.opts();
|
||||||
const sharedDeps = await getSharedDepsForLangConfig(
|
|
||||||
await readLangConfig(lang)
|
|
||||||
);
|
|
||||||
const installContents = await fs.readFile(
|
|
||||||
`build/lang/${lang}/install.bash`,
|
|
||||||
"utf-8"
|
|
||||||
);
|
|
||||||
const sharedInstallContents = await Promise.all(
|
|
||||||
sharedDeps.map(async (name) =>
|
|
||||||
fs.readFile(`build/shared/${name}/install.bash`)
|
|
||||||
)
|
|
||||||
);
|
|
||||||
const allInstallContents = [].concat.apply(
|
|
||||||
[installContents],
|
|
||||||
sharedInstallContents
|
|
||||||
);
|
|
||||||
const hash = await hashDockerfile(
|
|
||||||
"lang",
|
|
||||||
{
|
|
||||||
"riju:base": await getLocalImageLabel("riju:base", "riju.image-hash"),
|
|
||||||
},
|
|
||||||
{
|
|
||||||
salt: {
|
|
||||||
langHash: await getDebHash(`build/lang/${lang}/riju-lang-${lang}.deb`),
|
|
||||||
sharedHashes: (
|
|
||||||
await Promise.all(
|
|
||||||
sharedDeps.map(
|
|
||||||
async (name) =>
|
|
||||||
await getDebHash(`build/shared/${name}/riju-shared-${name}.deb`)
|
|
||||||
)
|
|
||||||
)
|
|
||||||
).sort(),
|
|
||||||
installHash: allInstallContents
|
|
||||||
.map((c) => crypto.createHash("sha1").update(c).digest("hex"))
|
|
||||||
.join(""),
|
|
||||||
},
|
|
||||||
}
|
|
||||||
);
|
|
||||||
const server = getServer();
|
const server = getServer();
|
||||||
await new Promise((resolve) => server.listen(8487, "localhost", resolve));
|
await new Promise((resolve) => server.listen(8487, "localhost", resolve));
|
||||||
try {
|
try {
|
||||||
|
@ -76,11 +33,11 @@ async function main() {
|
||||||
await runCommand(
|
await runCommand(
|
||||||
`docker build . -f docker/lang/Dockerfile ` +
|
`docker build . -f docker/lang/Dockerfile ` +
|
||||||
`--build-arg LANG=${lang} -t riju:lang-${lang} ` +
|
`--build-arg LANG=${lang} -t riju:lang-${lang} ` +
|
||||||
`--network host --no-cache --label riju.image-hash=${hash}`
|
`--network host --no-cache`
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
} finally {
|
} finally {
|
||||||
await server.close();
|
await new Promise((resolve) => server.close(resolve));
|
||||||
}
|
}
|
||||||
process.exit(0);
|
process.exit(0);
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,10 +0,0 @@
|
||||||
#!/usr/bin/env bash
|
|
||||||
|
|
||||||
set -euo pipefail
|
|
||||||
|
|
||||||
: ${AWS_ACCESS_KEY_ID}
|
|
||||||
: ${AWS_SECRET_ACCESS_KEY}
|
|
||||||
: ${DOCKER_REPO}
|
|
||||||
: ${S3_BUCKET}
|
|
||||||
|
|
||||||
make image shell I=ci CMD="tools/ci-run.bash" NI=1
|
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue