Skip to content

Commit

Permalink
WIP
Browse files Browse the repository at this point in the history
  • Loading branch information
webNeat committed Aug 16, 2024
1 parent 33b8029 commit f229fa6
Show file tree
Hide file tree
Showing 24 changed files with 324 additions and 109 deletions.
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
...

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: [],
}
}
2 changes: 1 addition & 1 deletion src/ansible/roles/create_hosty_directory.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ export function create_hosty_directory(path: string): Role {
tasks: [
builtin.file(
'Create hosty directory',
{ path, state: 'directory', mode: '0755', owner: '{{ansible_user}}', group: '{{ansible_user}}' },
{ path, state: 'directory', owner: '{{ansible_user}}', group: '{{ansible_user}}', mode: '0755' },
{ become: true },
),
],
Expand Down
71 changes: 45 additions & 26 deletions src/ansible/roles/create_service.ts
Original file line number Diff line number Diff line change
@@ -1,63 +1,82 @@
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 }))
tasks.push(builtin.file(`Create service directory for ${name}`, { path: service_dir, state: 'directory' }))

if (files_dir) {
files_dir = path.resolve(files_dir)
tasks.push(
builtin.copy(`Copy service files for ${name}`, { src: '{{item}}', dest: service_dir }, { register: 'files', with_fileglob: `${files_dir}/*` }),
)
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, '_')
const destfile = path.join(service_dir, filename)
const destdir = path.dirname(destfile)
tasks.push(
builtin.file(`Create service directory ${destdir}`, { path: destdir, state: 'directory' }, { register: var_name }),
builtin.copy(`Create service file ${filename}`, { content: files[filename], dest: destfile }, { register: var_name }),
)
restart_conditions.push(var_name + '.changed')
}
}

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
8 changes: 5 additions & 3 deletions src/ansible/roles/generate_ssh_key.ts
Original file line number Diff line number Diff line change
@@ -1,15 +1,17 @@
import { dirname } from 'path'
import { Role } from '../types.js'
import { builtin, crypto } from '../tasks/index.js'

export function generate_ssh_key(path: string, passphrase: string): Role {
return {
tasks: [
builtin.stat('Check if SSH key exists', { path }, { register: 'ssh_key' }),
builtin.file(
'Ensure .ssh directory exists',
{ path: '/home/{{ansible_user}}/.ssh', state: 'directory', owner: '{{ ansible_user }}', mode: '0700' },
{ become: true },
{ path: dirname(path), state: 'directory', owner: '{{ansible_user}}', mode: '0700' },
{ when: 'not ssh_key.stat.exists', become: true },
),
crypto.ssh_key('Generate SSH key', { type: 'rsa', path, passphrase }, { become: true }),
crypto.ssh_key('Generate SSH key', { type: 'rsa', path, passphrase }, { when: 'not ssh_key.stat.exists', become: true }),
],
handlers: [],
}
Expand Down
3 changes: 3 additions & 0 deletions src/ansible/roles/index.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,9 @@
export * as assert from './assert.js'
export * from './build_repo.js'
export * from './create_service.js'
export * from './create_hosty_directory.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 --batch --yes --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: [],
}
}
14 changes: 8 additions & 6 deletions src/ansible/roles/install_nixpacks.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,12 +4,14 @@ import { builtin } from '../tasks/index.js'
export function install_nixpacks(version: string): Role {
return {
tasks: [
builtin.get_url('Download nixpacks installer', {
url: `https://github.com/railwayapp/nixpacks/releases/download/v${version}/nixpacks-v${version}-amd64.deb`,
dest: '/tmp/nixpacks.deb',
mode: '0644',
}),
builtin.apt('Install nixpacks', { deb: '/tmp/nixpacks.deb' }, { become: true }),
builtin.shell(
`Install nixpacks`,
{
cmd: `curl -sSL https://nixpacks.com/install.sh | sudo bash`,
creates: `/usr/local/bin/nixpacks`,
},
{ become: true },
),
],
handlers: [],
}
Expand Down
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
11 changes: 9 additions & 2 deletions src/server.ts
Original file line number Diff line number Diff line change
@@ -1,18 +1,23 @@
import path from 'path'
import { roles } from './ansible/index.js'
import { Host } from './ansible/types.js'
import { Host, Role } from './ansible/types.js'
import { Server, ServerConfig } from './types.js'

export function server(config: ServerConfig): Server {
let connection = config.connection
if (!connection && config.name === 'localhost') connection = { type: 'local' }
if (!connection) connection = { type: 'ssh', address: config.name }
const hosty_dir = config.hosty_dir || '/srv/hosty'
return {
connection,
name: config.name,
ssh_key: config.ssh_key,
git_config: config.git_config,
hosty_dir: config.hosty_dir || '/srv/hosty',
hosty_dir: hosty_dir,
backups_dir: path.join(hosty_dir, 'backups'),
services_dir: path.join(hosty_dir, 'services'),
docker_network: config.docker_network || 'hosty',
docker_prefix: config.docker_prefix || '',
}
}

Expand Down Expand Up @@ -44,5 +49,7 @@ 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.create_hosty_directory(server.hosty_dir),
roles.install_caddy(`${server.services_dir}/*/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)]
}
Loading

0 comments on commit f229fa6

Please sign in to comment.