mirror of
https://github.com/replicatedhq/ttl.sh.git
synced 2026-07-12 07:39:19 +00:00
feat: migrate from Heroku to Google Cloud Platform
This commit is contained in:
5
.github/dependabot.yml
vendored
5
.github/dependabot.yml
vendored
@@ -3,11 +3,6 @@ version: 2
|
||||
updates:
|
||||
|
||||
# # Npm
|
||||
- package-ecosystem: "npm"
|
||||
directory: "/cloudflare"
|
||||
schedule:
|
||||
interval: "daily"
|
||||
|
||||
- package-ecosystem: "npm"
|
||||
directory: "/hooks"
|
||||
schedule:
|
||||
|
||||
57
.github/workflows/main.yaml
vendored
57
.github/workflows/main.yaml
vendored
@@ -1,57 +0,0 @@
|
||||
name: Deploy to Heroku
|
||||
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- main
|
||||
|
||||
jobs:
|
||||
release:
|
||||
runs-on: ubuntu-latest
|
||||
env:
|
||||
HEROKU_API_KEY: ${{secrets.HEROKU_API_KEY}}
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v2
|
||||
|
||||
- name: install heroku cli
|
||||
run: curl https://cli-assets.heroku.com/install-ubuntu.sh | sh
|
||||
|
||||
- name: heroku registry login
|
||||
run: heroku container:login
|
||||
|
||||
- name: build registry
|
||||
run: docker build -t registry.heroku.com/ttlsh/web registry
|
||||
|
||||
- name: push registry
|
||||
run: docker push registry.heroku.com/ttlsh/web
|
||||
|
||||
- name: release registry
|
||||
run: heroku container:release -a ttlsh web
|
||||
|
||||
- name: build nginx
|
||||
run: docker build -t registry.heroku.com/ttlsh-router/web nginx
|
||||
|
||||
- name: push nginx
|
||||
run: docker push registry.heroku.com/ttlsh-router/web
|
||||
|
||||
- name: release nginx
|
||||
run: heroku container:release -a ttlsh-router web
|
||||
|
||||
- name: build hooks
|
||||
run: docker build -f hooks/Dockerfile.hooks -t registry.heroku.com/ttlsh-hooks/web hooks
|
||||
|
||||
- name: push hooks
|
||||
run: docker push registry.heroku.com/ttlsh-hooks/web
|
||||
|
||||
- name: release hooks
|
||||
run: heroku container:release -a ttlsh-hooks web
|
||||
|
||||
- name: build reaper
|
||||
run: docker build -f hooks/Dockerfile.reap -t registry.heroku.com/ttlsh-hooks/reap hooks
|
||||
|
||||
- name: push reaper
|
||||
run: docker push registry.heroku.com/ttlsh-hooks/reap
|
||||
|
||||
- name: release reaper
|
||||
run: heroku container:release -a ttlsh-hooks reap
|
||||
2
.gitignore
vendored
2
.gitignore
vendored
@@ -1 +1,3 @@
|
||||
deploy
|
||||
terraform/staging/knowledge/*
|
||||
.terraform
|
||||
38
DEPLOYING.md
Normal file
38
DEPLOYING.md
Normal file
@@ -0,0 +1,38 @@
|
||||
# Deploying ttl.sh manually
|
||||
|
||||
## Hetzner Resources via Terraform
|
||||
|
||||
1. Doppler is installed and authenticated for use with the `ttl-sh` project
|
||||
a. Have a valid `DOPPLER_TOKEN` from `ttl-sh` set
|
||||
b. `echo $DOPPLER_TOKEN | doppler configure set token --scope /`
|
||||
2. Export necessary doppler values to ENVs
|
||||
a. `export HCLOUD_TOKEN=$(doppler secrets get HCLOUD_TOKEN --plain)`
|
||||
b. `export AWS_ACCESS_KEY_ID=$(doppler secrets get HCLOUD_S3_ACCESS_KEY --plain)`
|
||||
c. `export AWS_SECRET_ACCESS_KEY=$(doppler secrets get HCLOUD_S3_SECRET_KEY --plain)`
|
||||
3. `cd terraform`
|
||||
4. `terraform plan`
|
||||
a. Make sure plan looks as expected
|
||||
5. `terraform apply`
|
||||
a. approve with `yes`
|
||||
6. Successful run states `Apply complete!`
|
||||
|
||||
**NOTE**: For extra keys to be associated they must be added in the ttl-sh project. This is crucial to enable proper execution of installers.
|
||||
|
||||
## Build ttl.sh Docker images
|
||||
1. Docker and Docker Compose are installed
|
||||
2. `gcloud` CLI is installed and authenticated
|
||||
3. `gcloud auth configure-docker us-east4-docker.pkg.dev`
|
||||
4. `./build-and-push.sh`
|
||||
|
||||
## Server Setup and ttl.sh workloads
|
||||
|
||||
1. Doppler is installed and authenticated for use with the `ttl-sh` project
|
||||
a. Have a valid `DOPPLER_TOKEN` from `ttl-sh` set
|
||||
b. `echo $DOPPLER_TOKEN | doppler configure set token --scope /`
|
||||
2. `./install.sh`
|
||||
3. Successful run states:
|
||||
a. `Container ttlsh-redis Started`
|
||||
b. `Container ttlsh-hooks Started`
|
||||
b. `Container ttlsh-reaper Started`
|
||||
c. `Container ttlsh-registry Started`
|
||||
d. `Container ttlsh-nginx Started`
|
||||
6
build-and-push.sh
Executable file
6
build-and-push.sh
Executable file
@@ -0,0 +1,6 @@
|
||||
#!/usr/bin/env bash
|
||||
|
||||
# builds, tags, and pushes the images referenced in docker-compose.yaml
|
||||
|
||||
docker compose build
|
||||
docker compose push
|
||||
2
cloudflare/.gitignore
vendored
2
cloudflare/.gitignore
vendored
@@ -1,2 +0,0 @@
|
||||
dist/
|
||||
node_modules
|
||||
@@ -1,22 +0,0 @@
|
||||
|
||||
.PHONY: deps
|
||||
deps:
|
||||
yarn global add node-gyp
|
||||
yarn --silent --frozen-lockfile
|
||||
|
||||
.PHONY: prebuild
|
||||
prebuild:
|
||||
rm -rf build
|
||||
mkdir -p build
|
||||
|
||||
.PHONY: lint
|
||||
lint:
|
||||
npx tslint --project ./tsconfig.json --fix
|
||||
|
||||
.PHONY: build
|
||||
build: prebuild
|
||||
`yarn bin`/webpack
|
||||
|
||||
.PHONY: preview
|
||||
preview: build
|
||||
`yarn bin`/workers-preview < dist/bundle.js
|
||||
@@ -1,30 +0,0 @@
|
||||
{
|
||||
"name": "ttlsh-cloudflare-worker",
|
||||
"version": "0.0.1",
|
||||
"description": "Cloudflare Workers for ttl.sh",
|
||||
"main": "index.js",
|
||||
"scripts": {
|
||||
"test": "make test"
|
||||
},
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "git+https://github.com/replicatedhq/ttl.sh.git"
|
||||
},
|
||||
"author": "",
|
||||
"license": "Apache-2.0",
|
||||
"bugs": {
|
||||
"url": "https://github.com/replicatedhq/ttl.sh/issues"
|
||||
},
|
||||
"homepage": "https://github.com/replicatedhq/ttl.sh#readme",
|
||||
"devDependencies": {
|
||||
"awesome-typescript-loader": "^5.2.1",
|
||||
"typescript": "^3.4.5",
|
||||
"webpack": "^4.31.0",
|
||||
"webpack-cli": "^3.3.2",
|
||||
"workers-preview": "^1.0.6"
|
||||
},
|
||||
"dependencies": {
|
||||
"moment": "^2.24.0",
|
||||
"parse-duration": "^0.1.1"
|
||||
}
|
||||
}
|
||||
@@ -1,45 +0,0 @@
|
||||
if (typeof addEventListener === 'function') {
|
||||
addEventListener('fetch', (e: Event): void => {
|
||||
// work around as strict typescript check doesn't allow e to be of type FetchEvent
|
||||
const fe = e as FetchEvent
|
||||
fe.respondWith(proxyRequest(fe.request))
|
||||
});
|
||||
}
|
||||
|
||||
async function proxyRequest(r: Request): Promise<Response> {
|
||||
const url = new URL(r.url);
|
||||
|
||||
if (!isRegistryRequest(r)) {
|
||||
return fetch(`https://friendly-goldstine-7897fc.netlify.com/${url.pathname}`);
|
||||
}
|
||||
|
||||
if (isCatalogRequest(r)) {
|
||||
return new Response(null, {
|
||||
status: 401,
|
||||
});
|
||||
}
|
||||
|
||||
// Removing this code for now. It causes Docker to exit with a 1 exit code :(
|
||||
// const tagManifestParams = await tryParsePutTagRequest(r.method, url);
|
||||
// if (tagManifestParams) {
|
||||
// return handleTagManifestRequest(r, tagManifestParams);
|
||||
// }
|
||||
|
||||
return fetch(r);
|
||||
}
|
||||
|
||||
function isRegistryRequest(r: Request): boolean {
|
||||
const url = new URL(r.url);
|
||||
return url.pathname.startsWith(`/v2`);
|
||||
}
|
||||
|
||||
function isCatalogRequest(r: Request): boolean {
|
||||
const url = new URL(r.url);
|
||||
return url.pathname.endsWith(`v2/_catalog`);
|
||||
}
|
||||
|
||||
interface FetchEvent extends Event {
|
||||
request: Request;
|
||||
|
||||
respondWith(r: Promise<Response> | Response): Promise<Response>;
|
||||
}
|
||||
@@ -1,79 +0,0 @@
|
||||
import { TagManifestParams } from "./";
|
||||
import * as parseDuration from "parse-duration";
|
||||
import * as moment from "moment";
|
||||
|
||||
const durationRegex = /^(?=\d+[ywdhms])(( ?\d+y)?(?!\d))?(( ?\d+w)?(?!\d))?(( ?\d+d)?(?!\d))?(( ?\d+h)?(?!\d))?(( ?\d+m)?(?!\d))?(( ?\d+s)?(?!\d))?( ?\d+ms)?$/;
|
||||
|
||||
export function tryParsePutTagRequest(method: string, url: URL): TagManifestParams | void {
|
||||
const split = url.pathname.split("/");
|
||||
|
||||
// v2/ns/image/manifests/tag <- namespaced images
|
||||
// v2image/manifests/tag <- library images
|
||||
if (method !== "PUT") {
|
||||
return;
|
||||
}
|
||||
|
||||
if (split.length === 6 && split[4] === "manifests") {
|
||||
const tagManifestParams: TagManifestParams = {
|
||||
namespace: split[2],
|
||||
name: split[3],
|
||||
tag: split[5],
|
||||
};
|
||||
|
||||
return tagManifestParams;
|
||||
}
|
||||
|
||||
if (split.length === 5 && split[3] === "manifests") {
|
||||
const tagManifestParams: TagManifestParams = {
|
||||
namespace: "",
|
||||
name: split[2],
|
||||
tag: split[4],
|
||||
};
|
||||
|
||||
return tagManifestParams;
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
export async function handleTagManifestRequest(r: Request, params: TagManifestParams): Promise<Response> {
|
||||
r.headers["Accept"] = "application/vnd.docker.distribution.manifest.v2+json";
|
||||
|
||||
const upstreamResponse = await fetch(r);
|
||||
if (upstreamResponse.status != 201) {
|
||||
return upstreamResponse;
|
||||
}
|
||||
|
||||
let imageName;
|
||||
if (params.namespace === "") {
|
||||
imageName = params.name;
|
||||
} else {
|
||||
imageName = `${params.namespace}/${params.name}`;
|
||||
}
|
||||
|
||||
const teapotResponse = {
|
||||
"errors": [
|
||||
{
|
||||
"message": `\r \nimage ttl.sh/${imageName} is available now and will be automatically deleted ${expirationFromTag(params.tag)}\n\ttl.sh is contributed by Replicated (www.replicated.com)`
|
||||
}
|
||||
]
|
||||
};
|
||||
|
||||
return new Response(JSON.stringify(teapotResponse), {
|
||||
status: 418, // This is what works /shrug
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
function expirationFromTag(tag: string): string {
|
||||
let parsed = parseDuration(tag);
|
||||
if (!durationRegex.test(tag)) {
|
||||
// invalid duration, default to 24h
|
||||
parsed = 24 * 60 * 60 * 1000;
|
||||
}
|
||||
const now = new Date();
|
||||
const then = moment(now.getTime() + parsed);
|
||||
return then.fromNow();
|
||||
}
|
||||
@@ -1,2 +0,0 @@
|
||||
export * from "./tag_manifest";
|
||||
export * from "./handler";
|
||||
@@ -1,5 +0,0 @@
|
||||
export interface TagManifestParams {
|
||||
namespace: string;
|
||||
name: string;
|
||||
tag: string;
|
||||
}
|
||||
@@ -1,18 +0,0 @@
|
||||
import { tryParsePutTagRequest } from "./handler";
|
||||
import * as chai from "chai";
|
||||
|
||||
const testTryParsePutRequest = async () => {
|
||||
let result = await tryParsePutTagRequest("PUT", new URL("v2/random/more-random/manifests/2h"));
|
||||
expect(result).to.be.defined();
|
||||
expect(result.namespace).to.equal("random");
|
||||
expect(result.name).to.equal("more-random");
|
||||
expect(result.tag).to.equal("2h");
|
||||
|
||||
result = await tryParsePutTagRequest("PUT", new URL("v2/image-name/manifests/2h"));
|
||||
expect(result).to.be.defined();
|
||||
expect(result.namespace).to.equal("");
|
||||
expect(result.name).to.equal("image-name");
|
||||
expect(result.tag).to.equal("2h");
|
||||
|
||||
|
||||
}
|
||||
@@ -1,27 +0,0 @@
|
||||
{
|
||||
"compilerOptions": {
|
||||
"target": "es2017",
|
||||
"module": "commonjs",
|
||||
"lib": ["es6", "dom"],
|
||||
"noImplicitAny": false,
|
||||
"noEmitOnError": true,
|
||||
"sourceMap": true,
|
||||
"preserveConstEnums": true,
|
||||
"experimentalDecorators": true,
|
||||
"emitDecoratorMetadata": true,
|
||||
"strictNullChecks": true,
|
||||
"skipLibCheck": true,
|
||||
"allowJs": true,
|
||||
"outDir": "build",
|
||||
"pretty": true
|
||||
},
|
||||
"include": [
|
||||
"src/**/*.js",
|
||||
"src/**/*.ts"
|
||||
],
|
||||
"exclude": [
|
||||
"node_modules",
|
||||
"src/**/*_test.ts",
|
||||
"src/test/**/*.ts"
|
||||
]
|
||||
}
|
||||
@@ -1,40 +0,0 @@
|
||||
const path = require("path")
|
||||
const webpack = require('webpack');
|
||||
|
||||
module.exports = {
|
||||
entry: {
|
||||
bundle: path.join(__dirname, "./src/index.ts"),
|
||||
},
|
||||
|
||||
output: {
|
||||
filename: "bundle.js",
|
||||
path: path.join(__dirname, "dist"),
|
||||
},
|
||||
|
||||
mode: process.env.NODE_ENV || "development",
|
||||
|
||||
plugins: [
|
||||
// Ignore all locale files of moment.js
|
||||
new webpack.IgnorePlugin(/^\.\/locale$/, /moment$/),
|
||||
],
|
||||
|
||||
watchOptions: {
|
||||
ignored: /node_modules|dist|\.js/g,
|
||||
},
|
||||
|
||||
devtool: "cheap-module-source-map",
|
||||
|
||||
resolve: {
|
||||
extensions: [".ts", ".tsx", ".js", ".json"],
|
||||
plugins: [],
|
||||
},
|
||||
|
||||
module: {
|
||||
rules: [
|
||||
{
|
||||
test: /\.tsx?$/,
|
||||
loader: "awesome-typescript-loader",
|
||||
},
|
||||
],
|
||||
},
|
||||
}
|
||||
3013
cloudflare/yarn.lock
3013
cloudflare/yarn.lock
File diff suppressed because it is too large
Load Diff
94
docker-compose.yaml
Normal file
94
docker-compose.yaml
Normal file
@@ -0,0 +1,94 @@
|
||||
services:
|
||||
registry:
|
||||
image: us-east4-docker.pkg.dev/rgstry/ttl-sh-registry/registry:latest
|
||||
build:
|
||||
context: registry
|
||||
dockerfile: Dockerfile
|
||||
platforms:
|
||||
- "linux/amd64"
|
||||
container_name: ttlsh-registry
|
||||
restart: always
|
||||
pull_policy: always
|
||||
env_file: ".env"
|
||||
environment:
|
||||
- PORT=5000
|
||||
ports:
|
||||
- "5000:5000"
|
||||
- "5001:5001"
|
||||
networks:
|
||||
- ttlsh-network
|
||||
|
||||
hooks:
|
||||
image: us-east4-docker.pkg.dev/rgstry/ttl-sh-hooks/hooks:latest
|
||||
build:
|
||||
context: hooks
|
||||
dockerfile: Dockerfile.hooks
|
||||
platforms:
|
||||
- "linux/amd64"
|
||||
container_name: ttlsh-hooks
|
||||
restart: always
|
||||
pull_policy: always
|
||||
env_file: ".env"
|
||||
environment:
|
||||
- LOG_LEVEL=${LOG_LEVEL:-info}
|
||||
- NODE_ENV=${NODE_ENV:-production}
|
||||
- PORT=8000
|
||||
ports:
|
||||
- "8000:8000"
|
||||
networks:
|
||||
- ttlsh-network
|
||||
|
||||
reaper:
|
||||
image: us-east4-docker.pkg.dev/rgstry/ttl-sh-reap/reaper:latest
|
||||
build:
|
||||
context: hooks
|
||||
dockerfile: Dockerfile.reap
|
||||
platforms:
|
||||
- "linux/amd64"
|
||||
container_name: ttlsh-reaper
|
||||
restart: always
|
||||
pull_policy: always
|
||||
env_file: ".env"
|
||||
environment:
|
||||
- LOG_LEVEL=${LOG_LEVEL:-info}
|
||||
- NODE_ENV=${NODE_ENV:-production}
|
||||
networks:
|
||||
- ttlsh-network
|
||||
|
||||
redis:
|
||||
image: redis:7-alpine
|
||||
container_name: ttlsh-redis
|
||||
restart: always
|
||||
pull_policy: always
|
||||
env_file: ".env"
|
||||
ports:
|
||||
- "6379:6379"
|
||||
networks:
|
||||
- ttlsh-network
|
||||
volumes:
|
||||
- ./redis/data:/data
|
||||
command: redis-server --requirepass ${REDIS_PASSWORD}
|
||||
|
||||
nginx:
|
||||
image: us-east4-docker.pkg.dev/rgstry/ttl-sh-nginx/nginx:latest
|
||||
build:
|
||||
context: nginx
|
||||
dockerfile: Dockerfile
|
||||
platforms:
|
||||
- "linux/amd64"
|
||||
container_name: ttlsh-nginx
|
||||
restart: always
|
||||
pull_policy: always
|
||||
environment:
|
||||
- PORT=80
|
||||
ports:
|
||||
- "80:80"
|
||||
- "443:443"
|
||||
depends_on:
|
||||
- registry
|
||||
networks:
|
||||
- ttlsh-network
|
||||
|
||||
networks:
|
||||
ttlsh-network:
|
||||
driver: bridge
|
||||
27
install.sh
Executable file
27
install.sh
Executable file
@@ -0,0 +1,27 @@
|
||||
#!/usr/bin/env bash
|
||||
|
||||
REMOTE_HOST=178.156.198.215
|
||||
REMOTE_USER=root
|
||||
INSTALL_DIR=/opt/ttlsh
|
||||
|
||||
function setup_docker_compose() {
|
||||
ssh $REMOTE_USER@$REMOTE_HOST "mkdir -p $INSTALL_DIR"
|
||||
scp docker-compose.yaml $REMOTE_USER@$REMOTE_HOST:$INSTALL_DIR/docker-compose.yaml
|
||||
}
|
||||
|
||||
function setup_provisioner_script() {
|
||||
scp provision.sh $REMOTE_USER@$REMOTE_HOST:$INSTALL_DIR/provision.sh
|
||||
ssh $REMOTE_USER@$REMOTE_HOST "chmod a+x $INSTALL_DIR/provision.sh"
|
||||
}
|
||||
|
||||
function run_provisioner_script() {
|
||||
ssh $REMOTE_USER@$REMOTE_HOST "bash $INSTALL_DIR/provision.sh $1"
|
||||
}
|
||||
|
||||
function install() {
|
||||
setup_docker_compose
|
||||
setup_provisioner_script
|
||||
run_provisioner_script $DOPPLER_TOKEN
|
||||
}
|
||||
|
||||
install
|
||||
@@ -3,11 +3,8 @@ http {
|
||||
include mime.types;
|
||||
default_type application/octet-stream;
|
||||
|
||||
proxy_ssl_server_name on;
|
||||
resolver 8.8.8.8;
|
||||
|
||||
upstream upstream_ttlsh {
|
||||
server ttlsh.herokuapp.com:443;
|
||||
upstream registry {
|
||||
server ttlsh-registry:5000;
|
||||
}
|
||||
|
||||
server {
|
||||
@@ -25,12 +22,17 @@ http {
|
||||
|
||||
location /v2 {
|
||||
client_max_body_size 10000m;
|
||||
set $upstream upstream_ttlsh;
|
||||
proxy_pass https://$upstream;
|
||||
proxy_ssl_name ttlsh.herokuapp.com;
|
||||
proxy_set_header x-forwarded-host $host;
|
||||
client_body_timeout 3600s;
|
||||
proxy_connect_timeout 300s;
|
||||
proxy_send_timeout 3600s;
|
||||
proxy_read_timeout 3600s;
|
||||
send_timeout 3600s;
|
||||
proxy_pass http://registry;
|
||||
proxy_set_header X-Forwarded-Host $host;
|
||||
proxy_set_header X-Real-IP $remote_addr;
|
||||
proxy_set_header Host ttlsh.herokuapp.com;
|
||||
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
||||
proxy_set_header X-Forwarded-Proto $scheme;
|
||||
proxy_set_header Host $host;
|
||||
proxy_request_buffering off;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -170,7 +170,7 @@
|
||||
<div class="container col-12">
|
||||
<nav class="mb10"><a target="_blank" href="http://www.replicated.com/" class="nav-link secondary-color">Contributed by Replicated</a></nav>
|
||||
<div><a href="https://twitter.com/replicatedhq" target="_blank" class="socialicons accent-bg twitter"></a><a href="https://github.com/replicatedhq/ttl.sh" target="_blank" class="socialicons accent-bg github"></a></div>
|
||||
<div class="mt50"><span class="span secondary-color">©</span> <span class="span secondary-color">Replicated Inc 2022</span></div>
|
||||
<div class="mt50"><span class="span secondary-color">©</span> <span class="span secondary-color">Replicated Inc 2025</span></div>
|
||||
</div>
|
||||
</footer>
|
||||
|
||||
|
||||
94
provision.sh
Normal file
94
provision.sh
Normal file
@@ -0,0 +1,94 @@
|
||||
#!/usr/bin/env bash
|
||||
|
||||
DOPPLER_TOKEN=$1
|
||||
|
||||
function setup_doppler() {
|
||||
# install for ubuntu: https://docs.doppler.com/docs/cli
|
||||
sudo apt-get update && sudo apt-get install -y apt-transport-https ca-certificates curl gnupg
|
||||
curl -sLf --retry 3 --tlsv1.2 --proto "=https" 'https://packages.doppler.com/public/cli/gpg.DE2A7741A397C129.key' | sudo gpg --dearmor -o /usr/share/keyrings/doppler-archive-keyring.gpg
|
||||
echo "deb [signed-by=/usr/share/keyrings/doppler-archive-keyring.gpg] https://packages.doppler.com/public/cli/deb/debian any-version main" | sudo tee /etc/apt/sources.list.d/doppler-cli.list
|
||||
sudo apt-get update && sudo apt-get install doppler
|
||||
|
||||
# authenticate with service token
|
||||
echo $DOPPLER_TOKEN | doppler configure set token --scope /
|
||||
}
|
||||
|
||||
setup_docker() {
|
||||
# Adapted from:
|
||||
# * https://docs.docker.com/engine/install/ubuntu/#install-using-the-repository
|
||||
|
||||
# Add Docker's official GPG key:
|
||||
sudo apt-get update
|
||||
sudo apt-get install ca-certificates curl
|
||||
sudo install -m 0755 -d /etc/apt/keyrings
|
||||
sudo curl -fsSL https://download.docker.com/linux/ubuntu/gpg -o /etc/apt/keyrings/docker.asc
|
||||
sudo chmod a+r /etc/apt/keyrings/docker.asc
|
||||
|
||||
# Add the repository to Apt sources:
|
||||
echo \
|
||||
"deb [arch=$(dpkg --print-architecture) signed-by=/etc/apt/keyrings/docker.asc] https://download.docker.com/linux/ubuntu \
|
||||
$(. /etc/os-release && echo "${UBUNTU_CODENAME:-$VERSION_CODENAME}") stable" | \
|
||||
sudo tee /etc/apt/sources.list.d/docker.list > /dev/null
|
||||
sudo apt-get update
|
||||
|
||||
# install necessary packages
|
||||
sudo apt-get install docker-ce docker-ce-cli containerd.io docker-buildx-plugin docker-compose-plugin -y
|
||||
}
|
||||
|
||||
setup_gcloud() {
|
||||
# Adapted from:
|
||||
# * https://cloud.google.com/sdk/docs/downloads-interactive
|
||||
# * Artifact Registry setup commands
|
||||
|
||||
INSTALL_PATH="$(doppler secrets get GOOGLE_APPLICATION_CREDENTIALS --plain)"
|
||||
INSTALL_EMAIL="$(doppler secrets get GOOGLE_APPLICATION_CREDENTIALS_EMAIL --plain)"
|
||||
REGISTRY_URI="$(doppler secrets get GOOGLE_CLOUD_ARTIFACT_REGISTRY_URI --plain)"
|
||||
|
||||
mkdir -p "$(dirname "$INSTALL_PATH")"
|
||||
doppler secrets get GOOGLE_APPLICATION_CREDENTIALS_JSON --plain >"$INSTALL_PATH"
|
||||
chmod 600 "$INSTALL_PATH"
|
||||
|
||||
echo "export GOOGLE_APPLICATION_CREDENTIALS=\"$INSTALL_PATH\"" >> "$HOME/.bashrc"
|
||||
|
||||
# download the installer
|
||||
curl https://sdk.cloud.google.com | bash
|
||||
|
||||
# Explicitly add gcloud to PATH for this session
|
||||
export PATH="$HOME/google-cloud-sdk/bin:$PATH"
|
||||
|
||||
gcloud auth activate-service-account "$INSTALL_EMAIL" --key-file="$INSTALL_PATH" --quiet
|
||||
gcloud auth configure-docker "$REGISTRY_URI" --quiet
|
||||
}
|
||||
|
||||
setup_ttlsh_env() {
|
||||
for VAR in \
|
||||
GCS_KEY_ENCODED \
|
||||
HOOK_TOKEN \
|
||||
HOOK_URI \
|
||||
REDIS_PASSWORD \
|
||||
REDISCLOUD_URL \
|
||||
REPLREG_HOST \
|
||||
REPLREG_SECRET \
|
||||
REGISTRY_URL
|
||||
do
|
||||
echo "$VAR=$(doppler secrets get "$VAR" --plain)" >> /opt/ttlsh/.env
|
||||
done
|
||||
|
||||
chmod 600 "$ENV_FILE"
|
||||
}
|
||||
|
||||
function run_ttlsh()
|
||||
{
|
||||
docker compose -f /opt/ttlsh/docker-compose.yaml pull
|
||||
docker compose -f /opt/ttlsh/docker-compose.yaml up -d
|
||||
}
|
||||
|
||||
function provision() {
|
||||
setup_doppler
|
||||
setup_docker
|
||||
setup_gcloud
|
||||
setup_ttlsh_env
|
||||
run_ttlsh
|
||||
}
|
||||
|
||||
provision
|
||||
@@ -2,7 +2,6 @@ FROM registry:2.7.1
|
||||
|
||||
ADD ./entrypoint.sh /entrypoint.sh
|
||||
ADD ./config.yml /etc/docker/registry/config.yml
|
||||
ADD ./garbage-collect.sh /garbage-collect.sh
|
||||
|
||||
ENTRYPOINT ["/entrypoint.sh"]
|
||||
CMD ["/etc/docker/registry/config.yml"]
|
||||
|
||||
@@ -2,6 +2,7 @@ version: 0.1
|
||||
|
||||
log:
|
||||
level: info
|
||||
formatter: text
|
||||
|
||||
storage:
|
||||
delete:
|
||||
@@ -16,14 +17,20 @@ http:
|
||||
addr: 0.0.0.0:__PORT__
|
||||
secret: __REPLREG_SECRET__
|
||||
host: __REPLREG_HOST__
|
||||
debug:
|
||||
addr: 0.0.0.0:5001
|
||||
prometheus:
|
||||
enabled: false
|
||||
|
||||
notifications:
|
||||
events:
|
||||
includereferences: true
|
||||
endpoints:
|
||||
- name: rgstry-hooks
|
||||
- name: registry-hooks
|
||||
disabled: false
|
||||
url: __HOOK_URI__
|
||||
headers:
|
||||
Authorization: ["Token __HOOK_TOKEN__"]
|
||||
timeout: 200ms
|
||||
threshold: 3
|
||||
backoff: 5s
|
||||
|
||||
timeout: 1s
|
||||
threshold: 10
|
||||
backoff: 1s
|
||||
|
||||
@@ -1,7 +0,0 @@
|
||||
#!/bin/sh
|
||||
|
||||
while true; do
|
||||
sleep 1m
|
||||
echo "Starting garbage collection..."
|
||||
registry garbage-collect /etc/docker/registry/config.yml || true
|
||||
done
|
||||
24
terraform/.terraform.lock.hcl
generated
Normal file
24
terraform/.terraform.lock.hcl
generated
Normal file
@@ -0,0 +1,24 @@
|
||||
# This file is maintained automatically by "terraform init".
|
||||
# Manual edits may be lost in future updates.
|
||||
|
||||
provider "registry.terraform.io/hetznercloud/hcloud" {
|
||||
version = "1.52.0"
|
||||
constraints = "~> 1.45"
|
||||
hashes = [
|
||||
"h1:8Juiz/B0XWpSCJmIYLBoGqU14R0W9rudwVInfd7jBt0=",
|
||||
"zh:1e9bb6b6a2ea5f441638dbae2d60fbe04ff455f58a18c740b8b7913e2197d875",
|
||||
"zh:29c122e404ba331cfbadacc7f1294de5a31c9dfd60bdfe3e1b402271fc8e419c",
|
||||
"zh:2bd0ae2f0bb9f16b7753f59a08e57ac7230f9c471278d7882f81406b9426c8c7",
|
||||
"zh:4383206971873f6b5d81580a9a36e0158924f5816ebb6206b0cf2430e4e6a609",
|
||||
"zh:47e2ca1cfa18500e4952ab51dc357a0450d00a92da9ea03e452f1f3efe6bbf75",
|
||||
"zh:8e9fe90e3cea29bb7892b64da737642fc22b0106402df76c228a3cbe99663278",
|
||||
"zh:a2d69350a69c471ddb63bcc74e105e585319a0fc0f4d1b7f70569f6d2ece5824",
|
||||
"zh:a97abcc254e21c294e2d6b0fc9068acfd63614b097dda365f1c56ea8b0fd5f6b",
|
||||
"zh:aba8d72d4fe2e89c922d5446d329e5c23d00b28227b4666e6486ba18ea2ec278",
|
||||
"zh:ad36c333978c2d9e4bc43dcadcbff42fe771a8c5ef53d028bcacec8287bf78a7",
|
||||
"zh:cdb1e6903b9d2f0ad8845d4eb390fbe724ee2435fb045baeab38d4319e637682",
|
||||
"zh:df77b08757f3f36b8aadb33d73362320174047044414325c56a87983f48b5186",
|
||||
"zh:e07513d5ad387247092b5ae1c87e21a387fc51873b3f38eee616187e38b090a7",
|
||||
"zh:e2be02bdc59343ff4b9e26c3b93db7680aaf3e6ed13c8c4c4b144c74c2689915",
|
||||
]
|
||||
}
|
||||
148
terraform/main.tf
Normal file
148
terraform/main.tf
Normal file
@@ -0,0 +1,148 @@
|
||||
# Data Sources to query available locations
|
||||
data "hcloud_locations" "all" {}
|
||||
|
||||
# Select a specific location (you can change the filter criteria)
|
||||
data "hcloud_location" "main" {
|
||||
name = var.location
|
||||
}
|
||||
|
||||
# Query all available SSH keys in the account
|
||||
data "hcloud_ssh_keys" "all_keys" {}
|
||||
|
||||
# Centralized label management
|
||||
locals {
|
||||
# Common labels for all resources
|
||||
common_labels = merge({
|
||||
service = var.service_name
|
||||
environment = var.environment
|
||||
}, var.common_labels)
|
||||
|
||||
# Compute tier labels (merged with common)
|
||||
compute_labels = merge(local.common_labels, var.compute_tier_labels)
|
||||
}
|
||||
|
||||
# Network Configuration
|
||||
resource "hcloud_network" "main_network" {
|
||||
name = "${var.service_name}-network"
|
||||
ip_range = var.network_cidr
|
||||
}
|
||||
|
||||
# Private Subnet
|
||||
resource "hcloud_network_subnet" "main_subnet" {
|
||||
network_id = hcloud_network.main_network.id
|
||||
type = "cloud"
|
||||
network_zone = data.hcloud_location.main.network_zone
|
||||
ip_range = var.subnet_cidr
|
||||
}
|
||||
|
||||
# Firewall Configuration
|
||||
resource "hcloud_firewall" "main_firewall" {
|
||||
name = "${var.service_name}-firewall"
|
||||
|
||||
# Allow ICMP (ping)
|
||||
rule {
|
||||
direction = "in"
|
||||
protocol = "icmp"
|
||||
source_ips = [
|
||||
"0.0.0.0/0",
|
||||
"::/0"
|
||||
]
|
||||
}
|
||||
|
||||
# Allow HTTP
|
||||
rule {
|
||||
direction = "in"
|
||||
protocol = "tcp"
|
||||
port = "80"
|
||||
source_ips = [
|
||||
"0.0.0.0/0",
|
||||
"::/0"
|
||||
]
|
||||
}
|
||||
|
||||
# Allow HTTPS
|
||||
rule {
|
||||
direction = "in"
|
||||
protocol = "tcp"
|
||||
port = "443"
|
||||
source_ips = [
|
||||
"0.0.0.0/0",
|
||||
"::/0"
|
||||
]
|
||||
}
|
||||
|
||||
# Allow SSH (optional but usually needed)
|
||||
rule {
|
||||
direction = "in"
|
||||
protocol = "tcp"
|
||||
port = "22"
|
||||
source_ips = [
|
||||
"0.0.0.0/0",
|
||||
"::/0"
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
# Compute Server Instance (can be scaled to multiple instances)
|
||||
resource "hcloud_server" "instance1" {
|
||||
name = "${var.service_name}-compute-1" # Named to indicate it's a compute node
|
||||
image = var.server_image
|
||||
server_type = var.server_type
|
||||
location = data.hcloud_location.main.name
|
||||
firewall_ids = [hcloud_firewall.main_firewall.id]
|
||||
ssh_keys = data.hcloud_ssh_keys.all_keys.ssh_keys[*].id
|
||||
|
||||
labels = local.compute_labels
|
||||
|
||||
network {
|
||||
network_id = hcloud_network.main_network.id
|
||||
# IP will be auto-assigned from the subnet
|
||||
}
|
||||
|
||||
public_net {
|
||||
ipv4_enabled = true # Public IP for SSH management access
|
||||
ipv6_enabled = false
|
||||
}
|
||||
|
||||
depends_on = [
|
||||
hcloud_network_subnet.main_subnet
|
||||
]
|
||||
}
|
||||
|
||||
|
||||
# Outputs for debugging and DNS configuration
|
||||
output "server_primary_ip" {
|
||||
value = hcloud_server.instance1.ipv4_address
|
||||
description = "The server's primary IPv4 address to use for ttl.sh DNS A record"
|
||||
}
|
||||
|
||||
output "server_ipv6" {
|
||||
value = hcloud_server.instance1.ipv6_address
|
||||
description = "The server's IPv6 address"
|
||||
}
|
||||
|
||||
output "selected_location" {
|
||||
value = {
|
||||
name = data.hcloud_location.main.name
|
||||
description = data.hcloud_location.main.description
|
||||
network_zone = data.hcloud_location.main.network_zone
|
||||
}
|
||||
description = "Currently selected location details"
|
||||
}
|
||||
|
||||
output "ssh_keys_used" {
|
||||
value = {
|
||||
for key in data.hcloud_ssh_keys.all_keys.ssh_keys :
|
||||
key.name => key.fingerprint
|
||||
}
|
||||
description = "SSH keys that will be added to the server"
|
||||
}
|
||||
|
||||
output "server_details" {
|
||||
value = {
|
||||
name = hcloud_server.instance1.name
|
||||
private_ip = hcloud_server.instance1.network[*].ip
|
||||
labels = hcloud_server.instance1.labels
|
||||
}
|
||||
description = "Details of the deployed server instance"
|
||||
}
|
||||
10
terraform/provider.tf
Normal file
10
terraform/provider.tf
Normal file
@@ -0,0 +1,10 @@
|
||||
terraform {
|
||||
required_providers {
|
||||
hcloud = {
|
||||
source = "hetznercloud/hcloud"
|
||||
version = "~> 1.45"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
provider "hcloud" {}
|
||||
10
terraform/terraform.tf
Normal file
10
terraform/terraform.tf
Normal file
@@ -0,0 +1,10 @@
|
||||
terraform {
|
||||
backend "s3" {
|
||||
bucket = "ttl-sh-tf-state"
|
||||
key = "terraform.tfstate"
|
||||
endpoint = "hel1.your-objectstorage.com"
|
||||
region = "eu-central1"
|
||||
skip_credentials_validation = true
|
||||
skip_region_validation = true
|
||||
}
|
||||
}
|
||||
62
terraform/variables.tf
Normal file
62
terraform/variables.tf
Normal file
@@ -0,0 +1,62 @@
|
||||
# Service Configuration
|
||||
variable "service_name" {
|
||||
description = "Name of the service"
|
||||
type = string
|
||||
default = "ttl-sh"
|
||||
}
|
||||
|
||||
variable "environment" {
|
||||
description = "Environment name (staging, production)"
|
||||
type = string
|
||||
default = "production"
|
||||
}
|
||||
|
||||
# Network Configuration
|
||||
variable "network_cidr" {
|
||||
description = "CIDR range for the main network"
|
||||
type = string
|
||||
default = "10.0.0.0/16"
|
||||
}
|
||||
|
||||
variable "subnet_cidr" {
|
||||
description = "CIDR range for the subnet"
|
||||
type = string
|
||||
default = "10.0.0.0/24"
|
||||
}
|
||||
|
||||
# Compute Configuration
|
||||
variable "server_image" {
|
||||
description = "Image to use for servers"
|
||||
type = string
|
||||
default = "ubuntu-24.04"
|
||||
}
|
||||
|
||||
variable "server_type" {
|
||||
description = "Server type for compute instances"
|
||||
type = string
|
||||
default = "cpx41"
|
||||
}
|
||||
|
||||
|
||||
# Location Configuration
|
||||
variable "location" {
|
||||
description = "Hetzner Cloud location"
|
||||
type = string
|
||||
default = "ash"
|
||||
}
|
||||
|
||||
# Label Configuration
|
||||
variable "common_labels" {
|
||||
description = "Common labels to apply to all resources"
|
||||
type = map(string)
|
||||
default = {}
|
||||
}
|
||||
|
||||
variable "compute_tier_labels" {
|
||||
description = "Additional labels for compute tier resources"
|
||||
type = map(string)
|
||||
default = {
|
||||
tier = "compute"
|
||||
role = "worker"
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user