Skip to content

Commit

Permalink
WIP
Browse files Browse the repository at this point in the history
  • Loading branch information
webNeat committed Aug 15, 2024
1 parent 33b8029 commit 075ff20
Show file tree
Hide file tree
Showing 24 changed files with 331 additions and 81 deletions.
27 changes: 27 additions & 0 deletions .github/workflows/vagrant.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
name: Vagrant
on:
pull_request:
branches: [main]
jobs:
vagrant:
runs-on: ubuntu-latest

steps:
- name: Checkout repository
uses: actions/checkout@v3

- name: Install dependencies
run: |
sudo apt-get update
sudo apt-get install -y virtualbox
sudo apt-get install -y vagrant
- name: Start Vagrant VM
run: |
cd tests/vm
vagrant up
vagrant ssh -c "echo 'Vagrant is running!'"
- name: Destroy Vagrant VM
if: always()
run: vagrant destroy -f
15 changes: 15 additions & 0 deletions files.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
/srv/hosty/
services/
db-foo/
compose.yaml
...
app-foo/
compose.yaml
source.yaml
Caddyfile
...
backups/
db-foo/
yyyy-mm-dd_hh-mm-ss.sql.gz
...

Empty file added scripts/create-vm.sh
Empty file.
5 changes: 5 additions & 0 deletions scripts/install-vagrant.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
sudo apt install -y curl gnupg2 software-properties-common
curl -fsSL https://apt.releases.hashicorp.com/gpg | sudo apt-key add -
yes | sudo apt-add-repository "deb [arch=amd64] https://apt.releases.hashicorp.com $(lsb_release -cs) main"
sudo apt update -y
sudo apt install -y vagrant
1 change: 1 addition & 0 deletions src/ansible/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import { AnyTask, Host, Playbook, Role, Step } from './types.js'
export * from './types.js'
export * as tasks from './tasks/index.js'
export * as roles from './roles/index.js'
export * as operations from './operations.js'

export function task(data: AnyTask) {
return data
Expand Down
16 changes: 16 additions & 0 deletions src/ansible/operations.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
import { Role } from './types.js'

export function add_condition(role: Role, condition: string): Role {
for (const item of [...role.tasks, ...role.handlers]) {
if (item.when) item.when = `(${item.when}) and (${condition})`
else item.when = condition
}
return role
}

export function merge(roles: Role[]): Role {
return {
tasks: roles.flatMap((x) => x.tasks),
handlers: roles.flatMap((x) => x.handlers),
}
}
36 changes: 36 additions & 0 deletions src/ansible/roles/build_repo.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
import * as YAML from 'yaml'
import { Role } from '../types.js'
import { builtin } from '../tasks/index.js'
import path from 'path'

type Config = {
repo_url: string
branch: string
service_dir: string
image_name: string
}

export function build_repo(config: Config): Role {
const source_path = path.join(config.service_dir, 'source.yaml')
const source_content = { repo: config.repo_url, branch: config.branch, commit: '{{commit_hash}}' }
return {
tasks: [
builtin.command(`Get last commit hash`, { cmd: `git ls-remote ${config.repo_url} ${config.branch}` }, { register: 'git_ls_remote' }),
builtin.set_facts(`Set commit hash in a var`, { commit_hash: `{{git_ls_remote.stdout.split()[0]}}` }),
builtin.copy(`Write the source info`, { content: YAML.stringify(source_content), dest: source_path }, { register: 'source_file' }),
builtin.tempfile(`Create a temp dir to clone the repo`, { state: 'directory' }, { register: 'clone_dir', when: 'source_file.changed' }),
builtin.git(
`Clone the repo`,
{ repo: config.repo_url, version: config.branch, accept_hostkey: true, dest: '{{clone_dir.path}}' },
{ when: 'source_file.changed' },
),
builtin.command(
`Build the app using nixpacks`,
{ cmd: `nixpacks build {{clone_dir.path}} --name ${config.image_name}` },
{ when: 'source_file.changed' },
),
builtin.file(`Delete clone dir`, { path: '{{clone_dir.path}}', state: 'absent' }, { when: 'source_file.changed' }),
],
handlers: [],
}
}
65 changes: 40 additions & 25 deletions src/ansible/roles/create_service.ts
Original file line number Diff line number Diff line change
@@ -1,18 +1,37 @@
import path from 'path'
import * as YAML from 'yaml'
import { AnyTask, Role } from '../types.js'
import { builtin } from '../tasks/index.js'
import { ComposeFile, Service as ComposeService } from '../../compose.types.js'

export type ServiceConfig = {
type Config = {
name: string
service_dir: string
files_dir?: string
files?: Record<string, string>
docker_network: string
docker_compose: string
compose: ComposeService
}
export function create_service({ name, service_dir, docker_network, docker_compose, files_dir }: ServiceConfig): Role {

export function create_service({ name, service_dir, docker_network, compose, files_dir, files }: Config): Role {
const tasks: AnyTask[] = []
const handlers: AnyTask[] = []
const restart_conditions: string[] = []
const composeFile: ComposeFile = {
services: {
[name]: {
container_name: name,
networks: [docker_network],
restart: 'unless-stopped',
...compose,
},
},
networks: {
[docker_network]: {
external: true,
},
},
}

tasks.push(builtin.file(`Create service directory for ${name}`, { path: service_dir, state: 'directory', owner: '{{ansible_user}}' }, { become: true }))

Expand All @@ -22,42 +41,38 @@ export function create_service({ name, service_dir, docker_network, docker_compo
)
restart_conditions.push('files.changed')
}
if (files) {
for (const filename of Object.keys(files)) {
const var_name = 'additional_file_' + filename.replace(/[^a-zA-Z0-9]/g, '_')
tasks.push(
builtin.copy(`Create service file ${filename}`, { content: files[filename], dest: path.join(service_dir, filename) }, { register: var_name }),
)
restart_conditions.push(var_name + '.chnaged')
}
}

tasks.push(
builtin.copy(`Create compose.yaml for ${name}`, { content: docker_compose, dest: path.join(service_dir, 'compose.yaml') }, { register: 'compose' }),
builtin.copy(
`Create compose.yaml for ${name}`,
{ content: YAML.stringify(composeFile), dest: path.join(service_dir, 'compose.yaml') },
{ register: 'compose' },
),
)
restart_conditions.push('compose.changed')

// tasks.push(docker.docker_network(`Create docker network`, { name: docker_network, state: 'present' }, { become: true }))
tasks.push(
builtin.command(
`Check if docker network exists`,
{
cmd: `docker network inspect ${docker_network}`,
},
{
register: 'docker_network_check',
ignore_errors: true,
},
),
builtin.command(
`Create docker network`,
{
cmd: `docker network create ${docker_network}`,
},
{
when: 'docker_network_check.rc != 0',
become: true,
},
{ cmd: `docker network inspect ${docker_network}` },
{ register: 'docker_network_check', ignore_errors: true },
),
builtin.command(`Create docker network`, { cmd: `docker network create ${docker_network}` }, { when: 'docker_network_check.rc != 0', become: true }),
)

tasks.push(
builtin.command(
`Check if service ${name} is running`,
{
cmd: `docker inspect --format='{{"{{"}}.State.Running{{"}}"}}' ${name}`,
},
{ cmd: `docker inspect --format='{{"{{"}}.State.Running{{"}}"}}' ${name}` },
{ register: 'container_running', ignore_errors: true, become: true },
),
)
Expand Down
2 changes: 2 additions & 0 deletions src/ansible/roles/index.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
export * as assert from './assert.js'
export * from './build_repo.js'
export * from './create_service.js'
export * from './generate_ssh_key.js'
export * from './install_caddy.js'
export * from './install_docker.js'
export * from './install_git.js'
export * from './install_nixpacks.js'
41 changes: 41 additions & 0 deletions src/ansible/roles/install_caddy.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
import { Role } from '../types.js'
import { builtin } from '../tasks/index.js'

export function install_caddy(caddyfiles_pattern: string): Role {
return {
tasks: [
builtin.apt(
`Install Caddy's dependencies`,
{ name: ['debian-keyring', 'debian-archive-keyring', 'apt-transport-https', 'curl'], state: 'present' },
{ become: true },
),
builtin.shell(
`Add Caddy's official GPG key`,
{
cmd: `curl -1sLf 'https://dl.cloudsmith.io/public/caddy/stable/gpg.key' | sudo gpg --dearmor -o /usr/share/keyrings/caddy-stable-archive-keyring.gpg`,
},
{ become: true },
),
builtin.shell(
`Add Caddy's apt repository`,
{
cmd: `curl -1sLf 'https://dl.cloudsmith.io/public/caddy/stable/debian.deb.txt' | sudo tee /etc/apt/sources.list.d/caddy-stable.list`,
creates: `/etc/apt/sources.list.d/caddy-stable.list`,
},
{ become: true },
),
builtin.apt(`Update apt cache`, { update_cache: true }, { become: true }),
builtin.apt(`Install Caddy`, { name: 'caddy', state: 'present' }, { become: true }),
builtin.copy(
`Configure Caddy`,
{
dest: '/etc/caddy/Caddyfile',
content: `import ${caddyfiles_pattern}\n`,
},
{ become: true },
),
builtin.command(`Reload Caddy config`, { cmd: `sudo systemctl reload caddy` }, { become: true }),
],
handlers: [],
}
}
13 changes: 8 additions & 5 deletions src/ansible/tasks/builtin.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,8 @@ export function add_host(name: string, attrs: Host, common: CommonTaskAttrs = {}
return { name, 'ansible.builtin.add_host': attrs, ...common }
}

type SetFactAttrs = Record<string, any>
export function set_facts(name: string, attrs: SetFactAttrs, common: CommonTaskAttrs = {}): Task<'ansible.builtin.set_fact', SetFactAttrs> {
type SetFactsAttrs = Record<string, any>
export function set_facts(name: string, attrs: SetFactsAttrs, common: CommonTaskAttrs = {}): Task<'ansible.builtin.set_fact', SetFactsAttrs> {
return { name, 'ansible.builtin.set_fact': attrs, ...common }
}

Expand All @@ -19,9 +19,7 @@ export function stat(name: string, attrs: StatAttrs, common: CommonTaskAttrs = {
return { name, 'ansible.builtin.stat': attrs, ...common }
}

// add fn for set_fact

type FileAttrs = { path: string; state: 'file' | 'directory'; mode?: string; owner?: string; group?: string; recurse?: boolean }
type FileAttrs = { path: string; state: 'file' | 'directory' | 'absent'; mode?: string; owner?: string; group?: string; recurse?: boolean }
export function file(name: string, attrs: FileAttrs, common: CommonTaskAttrs = {}): Task<'ansible.builtin.file', FileAttrs> {
return { name, 'ansible.builtin.file': attrs, ...common }
}
Expand All @@ -31,6 +29,11 @@ export function lineinfile(name: string, attrs: LineInFileAttrs, common: CommonT
return { name, 'ansible.builtin.lineinfile': attrs, ...common }
}

type TempFileAttrs = { state: 'file' | 'directory' }
export function tempfile(name: string, attrs: TempFileAttrs, common: CommonTaskAttrs = {}): Task<'ansible.builtin.tempfile', TempFileAttrs> {
return { name, 'ansible.builtin.tempfile': attrs, ...common }
}

type TemplateAttrs = { src: string; dest: string }
export function template(name: string, attrs: TemplateAttrs, common: CommonTaskAttrs = {}): Task<'ansible.builtin.template', TemplateAttrs> {
return { name, 'ansible.builtin.template': attrs, ...common }
Expand Down
5 changes: 4 additions & 1 deletion src/server.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
import { roles } from './ansible/index.js'
import path from 'path'
import { operations, roles } from './ansible/index.js'
import { Host } from './ansible/types.js'
import { Server, ServerConfig } from './types.js'
import { unindent } from './utils.js'

export function server(config: ServerConfig): Server {
let connection = config.connection
Expand Down Expand Up @@ -44,5 +46,6 @@ export function get_setup_roles(server: Server) {
roles.install_git(server.git_config.name, server.git_config.email),
roles.generate_ssh_key(server.ssh_key.path, server.ssh_key.passphrase),
roles.install_nixpacks('1.24.0'),
roles.install_caddy(`${server.hosty_dir}/services/*/Caddyfile`),
]
}
36 changes: 36 additions & 0 deletions src/services/app.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
import path from 'path'
import { Role } from '../ansible/types.js'
import { App, AppConfig, Server } from '../types.js'
import { operations, roles } from '../ansible/index.js'

export function app(config: AppConfig): App {
return {
...config,
get_roles: (server) => get_roles(server, config),
}
}

function get_roles(server: Server, config: AppConfig): Role[] {
const service_dir = path.join(server.hosty_dir, 'services', config.name)
const compose = config.compose || {}
compose.image = config.name
compose.environment = { ...(config.env || {}), ...(compose.environment || {}) }
compose.expose ||= []
compose.expose.push('80')
if (config.exposed_port) {
compose.ports ||= []
compose.ports.push(`${config.exposed_port}:80`)
}

const steps = [
roles.build_repo({ repo_url: config.repo, branch: config.branch, service_dir, image_name: config.name }),
operations.add_condition(
roles.create_service({ name: config.name, compose, docker_network: server.docker_network, service_dir }),
'source_file.changed',
),
]
if (config.domain) {
// ...
}
return [operations.merge(steps)]
}
20 changes: 2 additions & 18 deletions src/services/container.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
import path from 'path'
import * as yaml from 'yaml'
import { Role } from '../ansible/types.js'
import { roles } from '../ansible/index.js'
import { ComposeFile } from '../compose.types.js'
Expand All @@ -10,28 +9,13 @@ export function container(config: ContainerConfig): Container {
}

function get_roles(server: Server, { name, compose, files_dir }: ContainerConfig): Role[] {
const composeFile: ComposeFile = {
services: {
[name]: {
container_name: name,
networks: [server.docker_network],
restart: 'unless-stopped',
...compose,
},
},
networks: {
[server.docker_network]: {
external: true,
},
},
}
return [
roles.create_service({
name,
compose,
files_dir,
service_dir: path.join(server.hosty_dir, '/services', name),
docker_network: server.docker_network,
docker_compose: yaml.stringify(composeFile),
service_dir: path.join(server.hosty_dir, '/services', name),
}),
]
}
Loading

0 comments on commit 075ff20

Please sign in to comment.