Compare commits

..

No commits in common. "dev" and "main" have entirely different histories.
dev ... main

177 changed files with 1765 additions and 24137 deletions

View File

@ -1,4 +0,0 @@
# To stay consistent with the V formatting style, we use tabs
UseTab: Always
IndentWidth: 4
TabWidth: 4

View File

@ -5,5 +5,6 @@ root = true
end_of_line = lf end_of_line = lf
insert_final_newline = true insert_final_newline = true
[*.{v,c,h}] [*.v]
# vfmt wants it :(
indent_style = tab indent_style = tab

24
.gitignore vendored
View File

@ -1,13 +1,13 @@
vieter.c *.c
/data/ data/
# Build artifacts # Build artifacts
/vieter vieter
/dvieter dvieter
/pvieter pvieter
/suvieter dvieterctl
/afvieter vieterctl
/vieter.c vieter.c
# Ignore testing files # Ignore testing files
*.pkg* *.pkg*
@ -23,11 +23,3 @@ v/
# gdb log file # gdb log file
gdb.txt gdb.txt
# Generated docs
_docs/
docs/resources/_gen/
/man/
# VLS logs
vls.log

6
.gitmodules vendored
View File

@ -1,6 +0,0 @@
[submodule "docs/themes/hugo-book"]
path = docs/themes/hugo-book
url = https://github.com/alex-shpak/hugo-book
[submodule "src/libvieter"]
path = src/libvieter
url = https://git.rustybever.be/vieter-v/libvieter

View File

@ -5,15 +5,11 @@ matrix:
platform: ${PLATFORM} platform: ${PLATFORM}
branches: [dev] branches: [dev]
skip_clone: true
pipeline: pipeline:
build: build:
image: 'git.rustybever.be/vieter-v/vieter-builder' image: 'menci/archlinuxarm:base-devel'
pull: true
commands: commands:
# Add the vieter repository so we can use the compiler
- echo -e '[vieter]\nServer = https://arch.r8r.be/$repo/$arch\nSigLevel = Optional' >> /etc/pacman.conf
# Update packages # Update packages
- pacman -Syu --noconfirm - pacman -Syu --noconfirm
# Create non-root user to perform build & switch to their home # Create non-root user to perform build & switch to their home
@ -22,12 +18,8 @@ pipeline:
- chown -R builder:builder "$PWD" - chown -R builder:builder "$PWD"
- "echo 'builder ALL=(ALL) NOPASSWD: ALL' >> /etc/sudoers" - "echo 'builder ALL=(ALL) NOPASSWD: ALL' >> /etc/sudoers"
- su builder - su builder
# Due to a bug with the V compiler, we can't just use the PKGBUILD from # Build the package
# inside the repo
- curl -o PKGBUILD -L https://git.rustybever.be/vieter-v/vieter/raw/branch/dev/PKGBUILD.dev
- makepkg -s --noconfirm --needed - makepkg -s --noconfirm --needed
when:
event: push
publish: publish:
image: 'curlimages/curl' image: 'curlimages/curl'
@ -36,5 +28,3 @@ pipeline:
- 'for pkg in $(ls -1 *.pkg*); do curl -XPOST -T "$pkg" -H "X-API-KEY: $VIETER_API_KEY" https://arch.r8r.be/vieter/publish; done' - 'for pkg in $(ls -1 *.pkg*); do curl -XPOST -T "$pkg" -H "X-API-KEY: $VIETER_API_KEY" https://arch.r8r.be/vieter/publish; done'
secrets: secrets:
- vieter_api_key - vieter_api_key
when:
event: push

View File

@ -1,41 +1,32 @@
variables:
- &vlang_image 'git.rustybever.be/vieter/vlang:5d4c9dc9fc11bf8648541c934adb64f27cb94e37-alpine3.17'
matrix: matrix:
PLATFORM: PLATFORM:
- 'linux/amd64' - linux/amd64
- 'linux/arm64' - linux/arm64
# I just don't have a performant enough runner for this platform
# - linux/arm/v7
# These checks already get performed on the feature branches
platform: ${PLATFORM} platform: ${PLATFORM}
pipeline: pipeline:
install-modules: # The default build isn't needed, as alpine switches to gcc for the compiler anyways
image: *vlang_image
pull: true
commands:
- export VMODULES=$PWD/.vmodules
- 'cd src && v install'
when:
event: [push, pull_request]
debug: debug:
image: *vlang_image image: 'chewingbever/vlang:latest'
pull: true
group: 'build'
commands: commands:
- export VMODULES=$PWD/.vmodules - make debug
- make
when: when:
event: [pull_request] event: push
branch:
exclude: [main]
prod: prod:
image: *vlang_image image: 'chewingbever/vlang:latest'
pull: true
environment: environment:
- LDFLAGS=-lz -lbz2 -llzma -lexpat -lzstd -llz4 -lsqlite3 -static - LDFLAGS=-lz -lbz2 -llzma -lexpat -lzstd -llz4 -static
group: 'build'
commands: commands:
- export VMODULES=$PWD/.vmodules - make prod
# Apparently this -D is *very* important
- CFLAGS='-DGC_THREADS=1' make prod
# Make sure the binary is actually statically built # Make sure the binary is actually statically built
- readelf -d pvieter - readelf -d pvieter
- du -h pvieter - du -h pvieter
@ -44,10 +35,10 @@ pipeline:
- strip -s pvieter - strip -s pvieter
- du -h pvieter - du -h pvieter
when: when:
event: [push, pull_request] event: push
upload: upload:
image: *vlang_image image: 'chewingbever/vlang:latest'
secrets: [ s3_username, s3_password ] secrets: [ s3_username, s3_password ]
commands: commands:
# https://gist.github.com/JustinTimperio/7c7115f87b775618637d67ac911e595f # https://gist.github.com/JustinTimperio/7c7115f87b775618637d67ac911e595f
@ -57,11 +48,10 @@ pipeline:
- export OBJ_PATH="/vieter/commits/$CI_COMMIT_SHA/vieter-$(echo '${PLATFORM}' | sed 's:/:-:g')" - export OBJ_PATH="/vieter/commits/$CI_COMMIT_SHA/vieter-$(echo '${PLATFORM}' | sed 's:/:-:g')"
- export SIG_STRING="PUT\n\n$CONTENT_TYPE\n$DATE\n$OBJ_PATH" - export SIG_STRING="PUT\n\n$CONTENT_TYPE\n$DATE\n$OBJ_PATH"
- export SIGNATURE="$(echo -en $SIG_STRING | openssl dgst -sha1 -hmac $S3_PASSWORD -binary | base64)" - export SIGNATURE=`echo -en $SIG_STRING | openssl sha1 -hmac $S3_PASSWORD -binary | base64`
- > - >
curl curl
--silent --silent
--fail
-XPUT -XPUT
-T pvieter -T pvieter
-H "Host: $URL" -H "Host: $URL"
@ -70,4 +60,4 @@ pipeline:
-H "Authorization: AWS $S3_USERNAME:$SIGNATURE" -H "Authorization: AWS $S3_USERNAME:$SIGNATURE"
https://$URL$OBJ_PATH https://$URL$OBJ_PATH
when: when:
event: [push, pull_request] event: push

View File

@ -0,0 +1,30 @@
branches: [main, dev]
platform: linux/amd64
depends_on:
- build
pipeline:
dev:
image: woodpeckerci/plugin-docker-buildx
secrets: [ docker_username, docker_password ]
settings:
repo: chewingbever/vieter
tag: dev
platforms: [ linux/arm64/v8, linux/amd64 ]
build_args_from_env:
- CI_COMMIT_SHA
when:
event: push
branch: dev
release:
image: woodpeckerci/plugin-docker-buildx
secrets: [ docker_username, docker_password ]
settings:
repo: chewingbever/vieter
auto_tag: true
platforms: [ linux/arm64/v8, linux/amd64 ]
build_args_from_env:
- CI_COMMIT_SHA
when:
event: tag

View File

@ -1,8 +1,6 @@
variables: # Yeah so this only works on tags so we'll worry about this later
- &vlang_image 'git.rustybever.be/vieter/vlang:5d4c9dc9fc11bf8648541c934adb64f27cb94e37-alpine3.17' platform: linux/amd64
branches: main
platform: 'linux/amd64'
branches: [ 'main' ]
depends_on: depends_on:
- build - build
@ -11,13 +9,12 @@ skip_clone: true
pipeline: pipeline:
prepare: prepare:
image: *vlang_image image: 'chewingbever/vlang:latest'
pull: true pull: true
secrets: [ s3_username, s3_password ] secrets: [ s3_username, s3_password ]
commands: commands:
- mc alias set s3/ https://s3.rustybever.be "$S3_USERNAME" "$S3_PASSWORD" - mc alias set s3/ https://s3.rustybever.be "$S3_USERNAME" "$S3_PASSWORD"
- mc cp -r "s3/vieter/commits/$CI_COMMIT_SHA" . - mc cp -r "s3/vieter/commits/$CI_COMMIT_SHA" .
- mv "$CI_COMMIT_SHA"/vieter-* .
when: when:
event: tag event: tag
@ -27,8 +24,9 @@ pipeline:
- gitea_release_api_key - gitea_release_api_key
settings: settings:
base_url: https://git.rustybever.be base_url: https://git.rustybever.be
files: vieter-* files: ${CI_COMMIT_SHA}/*
checksum: checksum:
- md5
- sha256 - sha256
title: ${CI_COMMIT_TAG} title: ${CI_COMMIT_TAG}
when: when:

View File

@ -0,0 +1,13 @@
# These checks already get performed on the feature branches
branches:
exclude: [ main, dev ]
platform: linux/amd64
pipeline:
lint:
image: 'chewingbever/vlang:latest'
pull: true
group: lint
commands:
- make lint
- make vet

View File

@ -1,40 +0,0 @@
matrix:
PLATFORM:
- linux/amd64
- linux/arm64
platform: ${PLATFORM}
branches: [main]
skip_clone: true
pipeline:
build:
image: 'git.rustybever.be/vieter-v/vieter-builder'
pull: true
commands:
# Add the vieter repository so we can use the compiler
- echo -e '[vieter]\nServer = https://arch.r8r.be/$repo/$arch\nSigLevel = Optional' >> /etc/pacman.conf
# Update packages
- pacman -Syu --noconfirm
# Create non-root user to perform build & switch to their home
- groupadd -g 1000 builder
- useradd -mg builder builder
- chown -R builder:builder "$PWD"
- "echo 'builder ALL=(ALL) NOPASSWD: ALL' >> /etc/sudoers"
- su builder
# Due to a bug with the V compiler, we can't just use the PKGBUILD from
# inside the repo
- curl -OL "https://git.rustybever.be/vieter-v/vieter/raw/tag/$CI_COMMIT_TAG/PKGBUILD"
- makepkg -s --noconfirm --needed
when:
event: tag
publish:
image: 'curlimages/curl'
commands:
# Publish the package
- 'for pkg in $(ls -1 *.pkg*); do curl -XPOST -T "$pkg" -H "X-API-KEY: $VIETER_API_KEY" https://arch.r8r.be/vieter/publish; done'
secrets:
- vieter_api_key
when:
event: tag

View File

@ -1,18 +0,0 @@
branches: [ 'dev' ]
platform: 'linux/amd64'
depends_on:
- 'docker'
skip_clone: true
pipeline:
webhooks:
image: 'curlimages/curl'
secrets:
- 'webhook_app'
- 'webhook_cron'
commands:
- 'curl -XPOST -s --fail $WEBHOOK_APP'
- 'curl -XPOST -s --fail $WEBHOOK_CRON'
when:
event: push

View File

@ -1,36 +0,0 @@
branches: [main, dev]
platform: 'linux/amd64'
depends_on:
- build
pipeline:
dev:
image: 'woodpeckerci/plugin-docker-buildx'
secrets:
- 'docker_username'
- 'docker_password'
settings:
repo: 'chewingbever/vieter'
tags:
- 'dev'
- ${CI_COMMIT_SHA}
platforms: [ 'linux/arm64/v8', 'linux/amd64' ]
build_args_from_env:
- 'CI_COMMIT_SHA'
when:
event: push
branch: dev
release:
image: 'woodpeckerci/plugin-docker-buildx'
secrets:
- 'docker_username'
- 'docker_password'
settings:
repo: 'chewingbever/vieter'
auto_tag: true
platforms: [ 'linux/arm64/v8', 'linux/amd64' ]
build_args_from_env:
- 'CI_COMMIT_SHA'
when:
event: tag

View File

@ -1,50 +0,0 @@
variables:
- &vlang_image 'git.rustybever.be/vieter/vlang:5d4c9dc9fc11bf8648541c934adb64f27cb94e37-alpine3.17'
platform: 'linux/amd64'
branches:
exclude: [ main ]
pipeline:
docs:
image: 'klakegg/hugo:ext-alpine'
group: 'generate'
commands:
- apk add git
- make docs
api-docs:
image: *vlang_image
pull: true
group: 'generate'
commands:
- make api-docs
slate-docs:
image: 'slatedocs/slate:v2.13.0'
group: 'generate'
# Slate requires a specific directory to run in
commands:
- cd docs/api
- bundle exec middleman build --clean
archive:
image: 'alpine'
commands:
- cp -r docs/api/build docs/public/api
- 'cd docs/public && tar czvf ../../docs.tar.gz *'
- 'cd ../../src/_docs && tar czvf ../../api-docs.tar.gz *'
when:
event: push
branch: dev
deploy:
image: 'curlimages/curl'
secrets:
- 'site_api_key'
commands:
- 'curl -XPOST --fail -s -H "Authorization: Bearer $SITE_API_KEY" -T docs.tar.gz https://rustybever.be/api/deploy?dir=docs-vieter'
- 'curl -XPOST --fail -s -H "Authorization: Bearer $SITE_API_KEY" -T api-docs.tar.gz https://rustybever.be/api/deploy?dir=api-docs-vieter'
when:
event: push
branch: dev

View File

@ -1,27 +0,0 @@
variables:
- &vlang_image 'git.rustybever.be/vieter/vlang:5d4c9dc9fc11bf8648541c934adb64f27cb94e37-alpine3.17'
# These checks already get performed on the feature branches
branches:
exclude: [ main ]
platform: 'linux/amd64'
pipeline:
# vfmt seems to get confused if these aren't present
install-modules:
image: *vlang_image
pull: true
commands:
- export VMODULES=$PWD/.vmodules
- 'cd src && v install'
when:
event: [pull_request]
lint:
image: *vlang_image
pull: true
commands:
- export VMODULES=$PWD/.vmodules
- make lint
when:
event: [pull_request]

View File

@ -1,45 +0,0 @@
variables:
- &vlang_image 'git.rustybever.be/vieter/vlang:5d4c9dc9fc11bf8648541c934adb64f27cb94e37-alpine3.17'
platform: 'linux/amd64'
branches:
exclude: [ main ]
depends_on:
- build
pipeline:
install-modules:
image: *vlang_image
pull: true
commands:
- export VMODULES=$PWD/.vmodules
- 'cd src && v install'
generate:
image: *vlang_image
commands:
# - curl -o vieter -L "https://s3.rustybever.be/vieter/commits/$CI_COMMIT_SHA/vieter-linux-amd64"
# - chmod +x vieter
- export VMODULES=$PWD/.vmodules
- make
- ./vieter man man
- cd man
# Generate an HTML page from each man page
- for f in $(ls -1 *.1); do mandoc -Thtml -O style=mandoc.css,man=%N.%S.html $f > "$f.html"; done
# Download the mandoc.css file from the official site
- curl -o mandoc.css -L https://mandoc.bsd.lv/mandoc.css
- tar czvf ../man.tar.gz *.html mandoc.css
deploy:
image: 'curlimages/curl'
secrets:
- 'site_api_key'
commands:
- 'curl -XPOST --fail -s -H "Authorization: Bearer $SITE_API_KEY" -T man.tar.gz https://rustybever.be/api/deploy?dir=man-vieter'
when:
event: push
branch: dev

View File

@ -1,30 +0,0 @@
variables:
- &vlang_image 'git.rustybever.be/vieter/vlang:5d4c9dc9fc11bf8648541c934adb64f27cb94e37-alpine3.17'
matrix:
PLATFORM:
- 'linux/amd64'
- 'linux/arm64'
branches:
exclude: [ main ]
platform: ${PLATFORM}
pipeline:
install-modules:
image: *vlang_image
pull: true
commands:
- export VMODULES=$PWD/.vmodules
- 'cd src && v install'
when:
event: [pull_request]
test:
image: *vlang_image
pull: true
commands:
- export VMODULES=$PWD/.vmodules
- make test
when:
event: [pull_request]

View File

@ -5,192 +5,9 @@ All notable changes to this project will be documented in this file.
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
## [Unreleased](https://git.rustybever.be/vieter-v/vieter/src/branch/dev) ## [Unreleased](https://git.rustybever.be/Chewing_Bever/vieter)
## [0.6.0](https://git.rustybever.be/vieter-v/vieter/src/tag/0.6.0) ## [0.2.0](https://git.rustybever.be/Chewing_Bever/vieter/src/tag/0.2.0)
### Added
* Metrics endpoint for Prometheus integration
* Search in list of targets using API & CLI
* Allow filtering targets by arch value
* Configurable global timeout for builds
### Changed
* Rewrote cron expression logic in C
* Updated codebase to V commit after 0.3.3
* Agents now use worker threads and no longer spawn a new thread for every
build
### Fixed
* Package upload now fails if TCP connection is closed before all bytes have
been received
### Removed
* Deprecated cron daemon
## [0.5.0](https://git.rustybever.be/vieter-v/vieter/src/tag/0.5.0)
### Added
* CLI commands for removing packages, arch-repos & repositories
## [0.5.0-rc.2](https://git.rustybever.be/vieter-v/vieter/src/tag/0.5.0-rc.2)
### Added
* API route for removing logs & accompanying CLI command
* Daemon for periodically removing old logs
* CLI flag to filter logs by specific exit codes
### Changed
* Use `--long-option` instead of `-long-option` for CLI
## [0.5.0-rc.1](https://git.rustybever.be/vieter-v/vieter/src/tag/0.5.0-rc.1)
### Added
* Allow specifying subdirectory inside Git repository
* Added option to deploy using agent-server architecture instead of cron daemon
* Allow scheduling builds on the server from the CLI tool instead of building
them locally
* Allow force-building packages, meaning the build won't check if the
repository is already up to date
### Changed
* Migrated codebase to V 0.3.2
* Cron expression parser now uses bitfields instead of bool arrays
### Fixed
* Arch value for target is now properly set if not provided
* Allow NULL values for branch in database
* Endpoint for adding targets now returns the correct id
* CLI now correctly errors and doesn't error when sending requests
* Fixed possible infinite loop when removing old build images
* Check whether build image still exists before starting build
* Don't run makepkg `prepare()` function twice
* Don't buffer stdout in Docker containers
## [0.4.0](https://git.rustybever.be/vieter-v/vieter/src/tag/0.4.0)
### Added
* Server port can now be configured
* Targets now have a 'kind' field describing whether it's a Git repository or a
URL to a PKGBUILD
* Targets with kind 'url' can provide a direct URL to a PKGBUILD instead of
providing a Git repository
* CLI commands for searching the AUR & directly adding packages
* HTTP routes for removing packages, arch-repos & repos
* All endpoints serving files now support HTTP byte range requests
* Better CLI UX
* When adding targets, the ID of the created target is returned
* The `-r` flag only shows raw data of action
* When adding a target, only ID is shown and not surrounding text
* Tabled output returns a tab-separated list (easy to script using
`cut`)
### Changed
* Moved all API routes under `/v1` namespace
* Renamed `vieter repos` to `vieter targets`
* Renamed `/api/v1/repos` namespace to `/api/v1/targets`
* Branch name for 'git' targets is now optional; if not provided, the
repository will be cloned with the default branch
* Build containers now explicitely set the PATH variable
* Refactor of web framework
* API endpoints now return id of newly created entries
* Repo POST requests now return information on published package
* `api` can no longer be used as a repository name
* CLI client now allows setting values to an empty value
### Removed
* md5 hashes are no longer calculated for packages
## [0.3.0](https://git.rustybever.be/vieter-v/vieter/src/tag/0.3.0)
Nothing besides bumping the versions.
## [0.3.0-rc.1](https://git.rustybever.be/vieter-v/vieter/src/tag/0.3.0-rc.1)
### Added
* Database migrations
* Improved GitRepo & BuildLog API
* Pagination using `limit` & `offset` query params
* GitRepo: filter by repo
* BuildLog: filter by start & end date, repo, exit code & arch
* CLI flags to take advantage of above API improvements
* Added CLI command to generate all man pages
* PKGBUILDs now install man pages
* Hosted CLI man pages ([vieter(1)](https://rustybever.be/man/vieter/vieter.1.html))
* Proper HTTP API docs ([link](https://rustybever.be/docs/vieter/api/))
### Changed
* Packages from target repo are available during builds
* This can be used as a basic way to support AUR dependencies, by adding
the dependencies to the same repository
* Every build now updates its packages first instead of solely relying on the
updated builder image
* Build logs now show commands being executed
### Fixed
* `POST /api/logs` now correctly uses epoch timestamps instead of strings
## [0.3.0-alpha.2](https://git.rustybever.be/vieter-v/vieter/src/tag/0.3.0-alpha.2)
### Added
* Web API for adding & querying build logs
* CLI commands to access build logs API
* Cron build logs are uploaded to above API
* Proper ASCII table output in CLI
* `vieter repos build id` command to run builds locally
### Removed
* `vieter build` command
* This command was used alongside cron for periodic builds, but this has
been replaced by `vieter cron`
### Changed
* `vieter build` command now only builds a single repository & uploads the
build logs
* Official Arch packages are now split between `vieter` & `vieter-git`
* `vieter` is the latest release
* `vieter-git` is the latest commit on the dev branch
* Full refactor of Docker socket code
## [0.3.0-alpha.1](https://git.rustybever.be/vieter-v/vieter/src/tag/0.3.0-alpha.1)
### Changed
* Switched from compiler fork to fully vanilla compiler mirror
* `download_dir`, `repos_file` & `repos_dir` config values have been replaced
with `data_dir`
* Storage of metadata (e.g. Git repositories) is now done using Sqlite
### Added
* Implemented own cron daemon for builder
* Build schedule can be configured globally or individually per repository
* Added CLI command to show detailed information per repo
### Fixed
* Binary no longer panics when an env var is missing
## [0.2.0](https://git.rustybever.be/vieter-v/vieter/src/tag/0.2.0)
### Changed ### Changed
@ -224,13 +41,13 @@ Nothing besides bumping the versions.
* Packages with unknown fields in .PKGINFO are now allowed * Packages with unknown fields in .PKGINFO are now allowed
* Old packages are now properly removed * Old packages are now properly removed
## [0.1.0](https://git.rustybever.be/vieter-v/vieter/src/tag/0.1.0) ## [0.1.0](https://git.rustybever.be/Chewing_Bever/vieter/src/tag/0.1.0)
### Changed ### Changed
* Improved logging * Improved logging
## [0.1.0-rc.1](https://git.rustybever.be/vieter-v/vieter/src/tag/0.1.0-rc.1) ## [0.1.0-rc.1](https://git.rustybever.be/Chewing_Bever/vieter/src/tag/0.1.0-rc.1)
### Added ### Added

View File

@ -1,4 +1,4 @@
FROM git.rustybever.be/chewing_bever/vlang:0.3.2 AS builder FROM chewingbever/vlang:latest AS builder
ARG TARGETPLATFORM ARG TARGETPLATFORM
ARG CI_COMMIT_SHA ARG CI_COMMIT_SHA
@ -23,8 +23,7 @@ RUN if [ -n "${CI_COMMIT_SHA}" ]; then \
"https://s3.rustybever.be/vieter/commits/${CI_COMMIT_SHA}/vieter-$(echo "${TARGETPLATFORM}" | sed 's:/:-:g')" && \ "https://s3.rustybever.be/vieter/commits/${CI_COMMIT_SHA}/vieter-$(echo "${TARGETPLATFORM}" | sed 's:/:-:g')" && \
chmod +x vieter ; \ chmod +x vieter ; \
else \ else \
cd src && v install && cd .. && \ LDFLAGS='-lz -lbz2 -llzma -lexpat -lzstd -llz4 -static' make prod && \
LDFLAGS='-lz -lbz2 -llzma -lexpat -lzstd -llz4 -lsqlite3 -static' make prod && \
mv pvieter vieter ; \ mv pvieter vieter ; \
fi fi
@ -32,13 +31,22 @@ RUN if [ -n "${CI_COMMIT_SHA}" ]; then \
FROM busybox:1.35.0 FROM busybox:1.35.0
ENV PATH=/bin \ ENV PATH=/bin \
VIETER_DATA_DIR=/data \ VIETER_REPOS_DIR=/data/repos \
VIETER_PKG_DIR=/data/pkgs VIETER_PKG_DIR=/data/pkgs \
VIETER_DOWNLOAD_DIR=/data/downloads \
VIETER_REPOS_FILE=/data/repos.json
COPY --from=builder /app/dumb-init /app/vieter /bin/ COPY --from=builder /app/dumb-init /app/vieter /bin/
HEALTHCHECK --interval=30s \
--timeout=3s \
--start-period=5s \
CMD /bin/wget --spider http://localhost:8000/health || exit 1
RUN mkdir /data && \ RUN mkdir /data && \
chown -R www-data:www-data /data chown -R www-data:www-data /data && \
mkdir -p '/var/spool/cron/crontabs' && \
echo '0 3 * * * /bin/vieter build' | crontab -
WORKDIR /data WORKDIR /data

View File

@ -1,20 +1,15 @@
# =====CONFIG===== # =====CONFIG=====
SRC_DIR := src SRC_DIR := src
SRCS != find '$(SRC_DIR)' -iname '*.v' SOURCES != find '$(SRC_DIR)' -iname '*.v'
V_PATH ?= v V_PATH ?= v/v
V := $(V_PATH) -showcc -gc boehm -d use_openssl -skip-unused V := $(V_PATH) -showcc -gc boehm
all: vieter all: vieter
# =====COMPILATION===== # =====COMPILATION=====
.PHONY: libvieter
libvieter:
make -C '$(SRC_DIR)/libvieter' CFLAGS='-O3'
# Regular binary # Regular binary
vieter: $(SOURCES) libvieter vieter: $(SOURCES)
$(V) -g -o vieter $(SRC_DIR) $(V) -g -o vieter $(SRC_DIR)
# Debug build using gcc # Debug build using gcc
@ -22,26 +17,25 @@ vieter: $(SOURCES) libvieter
# multi-threaded and causes issues when running vieter inside gdb. # multi-threaded and causes issues when running vieter inside gdb.
.PHONY: debug .PHONY: debug
debug: dvieter debug: dvieter
dvieter: $(SOURCES) libvieter dvieter: $(SOURCES)
$(V_PATH) -showcc -keepc -cg -o dvieter $(SRC_DIR) $(V_PATH) -showcc -keepc -cg -o dvieter $(SRC_DIR)
# Run the debug build inside gdb # Run the debug build inside gdb
.PHONY: gdb .PHONY: gdb
gdb: dvieter gdb: dvieter
gdb --args ./dvieter -f vieter.toml server gdb --args './dvieter -f vieter.toml server'
# Optimised production build # Optimised production build
.PHONY: prod .PHONY: prod
prod: pvieter prod: pvieter
pvieter: $(SOURCES) libvieter pvieter: $(SOURCES)
$(V) -o pvieter -prod $(SRC_DIR) $(V) -o pvieter -prod $(SRC_DIR)
# Only generate C code # Only generate C code
.PHONY: c .PHONY: c
c: $(SOURCES) libvieter c:
$(V) -o vieter.c $(SRC_DIR) $(V) -o vieter.c $(SRC_DIR)
# =====EXECUTION===== # =====EXECUTION=====
# Run the server in the default 'data' directory # Run the server in the default 'data' directory
.PHONY: run .PHONY: run
@ -52,60 +46,26 @@ run: vieter
run-prod: prod run-prod: prod
./pvieter -f vieter.toml server ./pvieter -f vieter.toml server
# =====DOCS=====
.PHONY: docs
docs:
rm -rf 'docs/public'
cd docs && hugo
.PHONY: api-docs
api-docs:
rm -rf '$(SRC_DIR)/_docs'
cd '$(SRC_DIR)' && v doc -all -f html -m -readme .
.PHONY: man
man: vieter
rm -rf man
./vieter man man
# =====OTHER===== # =====OTHER=====
# Linting
.PHONY: lint .PHONY: lint
lint: lint:
$(V) fmt -verify $(SRC_DIR) $(V) fmt -verify $(SRC_DIR)
$(V) vet -W $(SRC_DIR)
$(V_PATH) missdoc -p $(SRC_DIR)
@ [ $$($(V_PATH) missdoc -p $(SRC_DIR) | wc -l) = 0 ]
# Format the V codebase
# Formatting
.PHONY: fmt .PHONY: fmt
fmt: fmt:
$(V) fmt -w $(SRC_DIR) $(V) fmt -w $(SRC_DIR)
.PHONY: vet
vet:
$(V) vet -W $(SRC_DIR)
# Testing # Build & patch the V compiler
.PHONY: test .PHONY: v
test: libvieter v: v/v
$(V) -g test $(SRC_DIR) v/v:
git clone --single-branch --branch patches https://git.rustybever.be/Chewing_Bever/vieter-v v
make -C v
# Cleaning
.PHONY: clean
clean: clean:
rm -rf 'data' 'vieter' 'dvieter' 'pvieter' 'vieter.c' 'pkg' 'src/vieter' *.pkg.tar.zst 'suvieter' 'afvieter' '$(SRC_DIR)/_docs' 'docs/public' rm -rf 'data' 'vieter' 'dvieter' 'pvieter' 'vieter.c' 'dvieterctl' 'vieterctl' 'pkg' 'src/vieter'
make -C '$(SRC_DIR)/libvieter' clean
# =====EXPERIMENTAL=====
.PHONY: autofree
autofree: afvieter
afvieter: $(SOURCES)
$(V) -showcc -autofree -o afvieter $(SRC_DIR)
.PHONY: skip-unused
skip-unused: suvieter
suvieter: $(SOURCES)
$(V) -skip-unused -o suvieter $(SRC_DIR)

View File

@ -1,52 +1,34 @@
# vim: ft=bash
# Maintainer: Jef Roosens # Maintainer: Jef Roosens
pkgbase='vieter' pkgbase='vieter'
pkgname='vieter' pkgname='vieter'
pkgver='0.6.0' pkgver=0.1.0.rc1.r117.gc3ac00f
pkgrel=1 pkgrel=1
pkgdesc="Lightweight Arch repository server & package build system" depends=('glibc' 'openssl' 'libarchive' 'gc')
depends=('glibc' 'openssl' 'libarchive' 'sqlite') makedepends=('git' 'gcc')
makedepends=('git' 'vieter-vlang') arch=('x86_64' 'aarch64' 'armv7')
arch=('x86_64' 'aarch64') url='https://git.rustybever.be/Chewing_Bever/vieter'
url='https://git.rustybever.be/vieter-v/vieter'
license=('AGPL3') license=('AGPL3')
source=( source=($pkgname::git+https://git.rustybever.be/Chewing_Bever/vieter#branch=dev)
"$pkgname::git+https://git.rustybever.be/vieter-v/vieter#tag=${pkgver//_/-}" md5sums=('SKIP')
"libvieter::git+https://git.rustybever.be/vieter-v/libvieter"
)
md5sums=('SKIP' 'SKIP')
prepare() { pkgver() {
cd "${pkgname}" cd "$pkgname"
git describe --long --tags | sed 's/^v//;s/\([^-]*-g\)/r\1/;s/-/./g'
# Add the libvieter submodule
git submodule init
git config submodules.src/libvieter.url "${srcdir}/libvieter"
git -c protocol.file.allow=always submodule update
export VMODULES="${srcdir}/.vmodules"
cd src && v install
} }
build() { build() {
export VMODULES="$srcdir/.vmodules"
cd "$pkgname" cd "$pkgname"
make prod # Build the compiler
CFLAGS= make v
# The default CFLAGS for some reason causes vieter to segfault if used make prod
# inside the PKGBUILD. As a workaround, we use tcc to build a debug build
# that does work, so we can generate the manpages.
CFLAGS= LDFLAGS= make man
} }
package() { package() {
pkgdesc="Vieter is a lightweight implementation of an Arch repository server."
install -dm755 "$pkgdir/usr/bin" install -dm755 "$pkgdir/usr/bin"
install -Dm755 "$pkgname/pvieter" "$pkgdir/usr/bin/vieter"
install -dm755 "$pkgdir/usr/share/man/man1" install -Dm755 "$pkgbase/pvieter" "$pkgdir/usr/bin/vieter"
install -Dm644 "$pkgname/man"/*.1 "$pkgdir/usr/share/man/man1"
} }

View File

@ -1,60 +0,0 @@
# vim: ft=bash
# Maintainer: Jef Roosens
pkgbase='vieter-git'
pkgname='vieter-git'
pkgver=0.2.0.r25.g20112b8
pkgrel=1
pkgdesc="Lightweight Arch repository server & package build system (development version)"
depends=('glibc' 'openssl' 'libarchive' 'sqlite')
makedepends=('git' 'vieter-vlang')
arch=('x86_64' 'aarch64')
url='https://git.rustybever.be/vieter-v/vieter'
license=('AGPL3')
source=(
"${pkgname}::git+https://git.rustybever.be/vieter-v/vieter#branch=dev"
"libvieter::git+https://git.rustybever.be/vieter-v/libvieter"
)
md5sums=('SKIP' 'SKIP')
provides=('vieter')
conflicts=('vieter')
pkgver() {
cd "${pkgname}"
git describe --long --tags | sed 's/^v//;s/\([^-]*-g\)/r\1/;s/-/./g'
}
prepare() {
cd "${pkgname}"
# Add the libvieter submodule
git submodule init
git config submodules.src/libvieter.url "${srcdir}/libvieter"
git -c protocol.file.allow=always submodule update
export VMODULES="${srcdir}/.vmodules"
cd src && v install
}
build() {
export VMODULES="${srcdir}/.vmodules"
cd "${pkgname}"
make prod
# The default CFLAGS for some reason causes vieter to segfault if used
# inside the PKGBUILD. As a workaround, we use tcc to build a debug build
# that does work, so we can generate the manpages.
CFLAGS= LDFLAGS= make man
}
package() {
install -dm755 "${pkgdir}/usr/bin"
install -Dm755 "${pkgname}/pvieter" "${pkgdir}/usr/bin/vieter"
install -dm755 "${pkgdir}/usr/share/man/man1"
install -Dm644 "${pkgname}/man"/*.1 "${pkgdir}/usr/share/man/man1"
}

View File

@ -1,11 +1,8 @@
# Vieter # Vieter
I host documentation for Vieter over at https://rustybever.be/docs/vieter/. API ## Documentation
documentation for the current codebase can be found at
https://rustybever.be/api-docs/vieter/.
For more information, questions or just a chat, there's I host documentation for Vieter over at https://rustybever.be/docs/vieter/.
[#vieter:rustybever.be](https://matrix.to/#/#vieter:rustybever.be) on Matrix!
## Overview ## Overview
@ -21,8 +18,17 @@ quicker.
I chose [V](https://vlang.io/) as I've been very intrigued by this language for I chose [V](https://vlang.io/) as I've been very intrigued by this language for
a while now. I wanted a fast language that I could code while relaxing, without a while now. I wanted a fast language that I could code while relaxing, without
having to exert too much mental effort & V seemed like the right choice for having to exert too much mental effort & V seemed like the right choice for
that. Sadly, this didn't quite turn out the way I expected, but I'm sticking that.
with it anyways ;p
### Custom Compiler
Currently, this program only works with a very slightly modified version of the
V standard library, and therefore the compiler. The source code for this fork
can be found [here](https://git.rustybever.be/Chewing_Bever/vieter-v). You can
obtain this modified version of the compiler by running `make v`, which will
clone & build the compiler. Afterwards, all make commands that require the V
compiler will use this new binary. I try to keep this fork as up to date with
upstream as possible.
## Features ## Features
@ -36,62 +42,11 @@ with it anyways ;p
## Building ## Building
Besides a V installer, Vieter also requires the following libraries to work: In order to build Vieter, you'll need a couple of libraries:
* gc
* libarchive * libarchive
* openssl * openssl
* sqlite3
Vieter also depends on some external V modules which you can install using `cd Before building Vieter, you'll have to build the compiler using `make v`.
src && v install`. Make sure to keep these dependencies up to date using `v Afterwards, run `make` to build the debug binary.
update`.
### Compiler
V is developed using a specific compiler commit that is usually updated
whenever a new version is released. Information on this can be found in the
[tools](https://git.rustybever.be/vieter-v/tools) repository.
## Contributing
If you wish to contribute to the project, please take note of the following:
* Rebase instead of merging whenever possible, e.g. when updating your branch
with the dev branch.
* Please follow the
[Conventional Commits](https://www.conventionalcommits.org/) style for your
commit messages.
### Writing documentation
The `docs` directory contains a Hugo site consisting of all user &
administrator documentation. `docs/api` on the other hand is a
[Slate](https://github.com/slatedocs/slate) project describing the HTTP web
API.
To modify the Hugo documentation, you'll need to install Hugo. Afterwards, you
can use the following commands inside the `docs` directory:
```sh
# Build the documentation
hugo
# Host an auto-refreshing web server with the documentation. Important to note
# is that the files will be at `http://localhost:1313/docs/vieter` instead of
# just `http://localhost:1313/`
hugo server
```
For the Slate docs, I personally just start a docker container:
```sh
docker run \
--rm \
-p 4567:4567 \
--name slate \
-v $(pwd)/docs/api/source:/srv/slate/source slatedocs/slate serve
```
This will make the Slate docs available at http://localhost:4567. Sadly, this
server doesn't auto-refresh, so you'll have to manually refresh your browser
every time you make a change.

2
docs/.gitignore vendored
View File

@ -1,2 +0,0 @@
.hugo_build.lock
/public/

View File

@ -1,17 +0,0 @@
platform: 'linux/amd64'
branches: 'main'
pipeline:
release:
image: 'klakegg/hugo:alpine'
commands:
- apk add git
- hugo
- 'cd public && tar czvf ../public.tar.gz *'
deploy:
image: 'curlimages/curl'
secrets:
- 'api_key'
commands:
- 'curl -XPOST --fail -s -H "Authorization: Bearer $API_KEY" -T public.tar.gz https://rustybever.be/api/deploy?dir=docs'

View File

@ -1,9 +0,0 @@
MIT License
Copyright (c) <year> <copyright holders>
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

View File

@ -1,3 +0,0 @@
# docs
Repository containing docs for various personal projects I've made.

View File

@ -1,12 +0,0 @@
.git/
.github/
build/
.editorconfig
.gitattributes
.gitignore
CHANGELOG.md
CODE_OF_CONDUCT.md
deploy.sh
font-selection.json
README.md
Vagrantfile

View File

@ -1,18 +0,0 @@
# EditorConfig is awesome: https://EditorConfig.org
# Top-most EditorConfig file
root = true
# Unix-style newlines with a newline ending every file
[*]
end_of_line = lf
insert_final_newline = true
indent_style = space
indent_size = 2
trim_trailing_whitespace = true
[*.rb]
charset = utf-8
[*.md]
trim_trailing_whitespace = false

View File

@ -1 +0,0 @@
source/javascripts/lib/* linguist-vendored

27
docs/api/.gitignore vendored
View File

@ -1,27 +0,0 @@
*.gem
*.rbc
.bundle
.config
coverage
InstalledFiles
lib/bundler/man
pkg
rdoc
spec/reports
test/tmp
test/version_tmp
tmp
*.DS_STORE
build/
.cache
.vagrant
.sass-cache
# YARD artifacts
.yardoc
_yardoc
doc/
.idea/
# Vagrant artifacts
ubuntu-*-console.log

View File

@ -1,13 +0,0 @@
ruby '>= 2.6'
source 'https://rubygems.org'
# Middleman
gem 'middleman', '~> 4.4'
gem 'middleman-syntax', '~> 3.2'
gem 'middleman-autoprefixer', '~> 3.0'
gem 'middleman-sprockets', '~> 4.1'
gem 'rouge', '~> 3.21'
gem 'redcarpet', '~> 3.5.0'
gem 'nokogiri', '~> 1.13.3'
gem 'sass'
gem 'webrick'

View File

@ -1,145 +0,0 @@
GEM
remote: https://rubygems.org/
specs:
activesupport (6.1.4.1)
concurrent-ruby (~> 1.0, >= 1.0.2)
i18n (>= 1.6, < 2)
minitest (>= 5.1)
tzinfo (~> 2.0)
zeitwerk (~> 2.3)
addressable (2.8.0)
public_suffix (>= 2.0.2, < 5.0)
autoprefixer-rails (10.2.5.0)
execjs (< 2.8.0)
backports (3.21.0)
coffee-script (2.4.1)
coffee-script-source
execjs
coffee-script-source (1.12.2)
concurrent-ruby (1.1.9)
contracts (0.13.0)
dotenv (2.7.6)
erubis (2.7.0)
execjs (2.7.0)
fast_blank (1.0.1)
fastimage (2.2.5)
ffi (1.15.4)
haml (5.2.2)
temple (>= 0.8.0)
tilt
hamster (3.0.0)
concurrent-ruby (~> 1.0)
hashie (3.6.0)
i18n (1.6.0)
concurrent-ruby (~> 1.0)
kramdown (2.3.1)
rexml
listen (3.0.8)
rb-fsevent (~> 0.9, >= 0.9.4)
rb-inotify (~> 0.9, >= 0.9.7)
memoist (0.16.2)
middleman (4.4.2)
coffee-script (~> 2.2)
haml (>= 4.0.5)
kramdown (>= 2.3.0)
middleman-cli (= 4.4.2)
middleman-core (= 4.4.2)
middleman-autoprefixer (3.0.0)
autoprefixer-rails (~> 10.0)
middleman-core (>= 4.0.0)
middleman-cli (4.4.2)
thor (>= 0.17.0, < 2.0)
middleman-core (4.4.2)
activesupport (>= 6.1, < 7.0)
addressable (~> 2.4)
backports (~> 3.6)
bundler (~> 2.0)
contracts (~> 0.13.0)
dotenv
erubis
execjs (~> 2.0)
fast_blank
fastimage (~> 2.0)
hamster (~> 3.0)
hashie (~> 3.4)
i18n (~> 1.6.0)
listen (~> 3.0.0)
memoist (~> 0.14)
padrino-helpers (~> 0.15.0)
parallel
rack (>= 1.4.5, < 3)
sassc (~> 2.0)
servolux
tilt (~> 2.0.9)
toml
uglifier (~> 3.0)
webrick
middleman-sprockets (4.1.1)
middleman-core (~> 4.0)
sprockets (>= 3.0)
middleman-syntax (3.2.0)
middleman-core (>= 3.2)
rouge (~> 3.2)
mini_portile2 (2.8.0)
minitest (5.14.4)
nokogiri (1.13.4)
mini_portile2 (~> 2.8.0)
racc (~> 1.4)
padrino-helpers (0.15.1)
i18n (>= 0.6.7, < 2)
padrino-support (= 0.15.1)
tilt (>= 1.4.1, < 3)
padrino-support (0.15.1)
parallel (1.21.0)
parslet (2.0.0)
public_suffix (4.0.6)
racc (1.6.0)
rack (2.2.3)
rb-fsevent (0.11.0)
rb-inotify (0.10.1)
ffi (~> 1.0)
redcarpet (3.5.1)
rexml (3.2.5)
rouge (3.28.0)
sass (3.7.4)
sass-listen (~> 4.0.0)
sass-listen (4.0.0)
rb-fsevent (~> 0.9, >= 0.9.4)
rb-inotify (~> 0.9, >= 0.9.7)
sassc (2.4.0)
ffi (~> 1.9)
servolux (0.13.0)
sprockets (3.7.2)
concurrent-ruby (~> 1.0)
rack (> 1, < 3)
temple (0.8.2)
thor (1.1.0)
tilt (2.0.10)
toml (0.3.0)
parslet (>= 1.8.0, < 3.0.0)
tzinfo (2.0.4)
concurrent-ruby (~> 1.0)
uglifier (3.2.0)
execjs (>= 0.3.0, < 3)
webrick (1.7.0)
zeitwerk (2.5.1)
PLATFORMS
ruby
DEPENDENCIES
middleman (~> 4.4)
middleman-autoprefixer (~> 3.0)
middleman-sprockets (~> 4.1)
middleman-syntax (~> 3.2)
nokogiri (~> 1.13.3)
redcarpet (~> 3.5.0)
rouge (~> 3.21)
sass
webrick
RUBY VERSION
ruby 2.7.2p137
BUNDLED WITH
2.2.22

View File

@ -1,63 +0,0 @@
# Unique header generation
require './lib/unique_head.rb'
# Markdown
set :markdown_engine, :redcarpet
set :markdown,
fenced_code_blocks: true,
smartypants: true,
disable_indented_code_blocks: true,
prettify: true,
strikethrough: true,
tables: true,
with_toc_data: true,
no_intra_emphasis: true,
renderer: UniqueHeadCounter
# Assets
set :css_dir, 'stylesheets'
set :js_dir, 'javascripts'
set :images_dir, 'images'
set :fonts_dir, 'fonts'
# Activate the syntax highlighter
activate :syntax
ready do
require './lib/monokai_sublime_slate.rb'
require './lib/multilang.rb'
end
activate :sprockets
activate :autoprefixer do |config|
config.browsers = ['last 2 version', 'Firefox ESR']
config.cascade = false
config.inline = true
end
# Github pages require relative links
activate :relative_assets
set :relative_links, true
# Build Configuration
configure :build do
# We do want to hash woff and woff2 as there's a bug where woff2 will use
# woff asset hash which breaks things. Trying to use a combination of ignore and
# rewrite_ignore does not work as it conflicts weirdly with relative_assets. Disabling
# the .woff2 extension only does not work as .woff will still activate it so have to
# have both. See https://github.com/slatedocs/slate/issues/1171 for more details.
activate :asset_hash, :exts => app.config[:asset_extensions] - %w[.woff .woff2]
# If you're having trouble with Middleman hanging, commenting
# out the following two lines has been known to help
activate :minify_css
activate :minify_javascript
# activate :gzip
end
# Deploy Configuration
# If you want Middleman to listen on a different port, you can set that below
set :port, 4567
helpers do
require './lib/toc_data.rb'
end

View File

@ -1,148 +0,0 @@
{
"IcoMoonType": "selection",
"icons": [
{
"icon": {
"paths": [
"M438.857 73.143q119.429 0 220.286 58.857t159.714 159.714 58.857 220.286-58.857 220.286-159.714 159.714-220.286 58.857-220.286-58.857-159.714-159.714-58.857-220.286 58.857-220.286 159.714-159.714 220.286-58.857zM512 785.714v-108.571q0-8-5.143-13.429t-12.571-5.429h-109.714q-7.429 0-13.143 5.714t-5.714 13.143v108.571q0 7.429 5.714 13.143t13.143 5.714h109.714q7.429 0 12.571-5.429t5.143-13.429zM510.857 589.143l10.286-354.857q0-6.857-5.714-10.286-5.714-4.571-13.714-4.571h-125.714q-8 0-13.714 4.571-5.714 3.429-5.714 10.286l9.714 354.857q0 5.714 5.714 10t13.714 4.286h105.714q8 0 13.429-4.286t6-10z"
],
"attrs": [],
"isMulticolor": false,
"tags": [
"exclamation-circle"
],
"defaultCode": 61546,
"grid": 14
},
"attrs": [],
"properties": {
"id": 100,
"order": 4,
"prevSize": 28,
"code": 58880,
"name": "exclamation-sign",
"ligatures": ""
},
"setIdx": 0,
"iconIdx": 0
},
{
"icon": {
"paths": [
"M585.143 786.286v-91.429q0-8-5.143-13.143t-13.143-5.143h-54.857v-292.571q0-8-5.143-13.143t-13.143-5.143h-182.857q-8 0-13.143 5.143t-5.143 13.143v91.429q0 8 5.143 13.143t13.143 5.143h54.857v182.857h-54.857q-8 0-13.143 5.143t-5.143 13.143v91.429q0 8 5.143 13.143t13.143 5.143h256q8 0 13.143-5.143t5.143-13.143zM512 274.286v-91.429q0-8-5.143-13.143t-13.143-5.143h-109.714q-8 0-13.143 5.143t-5.143 13.143v91.429q0 8 5.143 13.143t13.143 5.143h109.714q8 0 13.143-5.143t5.143-13.143zM877.714 512q0 119.429-58.857 220.286t-159.714 159.714-220.286 58.857-220.286-58.857-159.714-159.714-58.857-220.286 58.857-220.286 159.714-159.714 220.286-58.857 220.286 58.857 159.714 159.714 58.857 220.286z"
],
"attrs": [],
"isMulticolor": false,
"tags": [
"info-circle"
],
"defaultCode": 61530,
"grid": 14
},
"attrs": [],
"properties": {
"id": 85,
"order": 3,
"name": "info-sign",
"prevSize": 28,
"code": 58882
},
"setIdx": 0,
"iconIdx": 2
},
{
"icon": {
"paths": [
"M733.714 419.429q0-16-10.286-26.286l-52-51.429q-10.857-10.857-25.714-10.857t-25.714 10.857l-233.143 232.571-129.143-129.143q-10.857-10.857-25.714-10.857t-25.714 10.857l-52 51.429q-10.286 10.286-10.286 26.286 0 15.429 10.286 25.714l206.857 206.857q10.857 10.857 25.714 10.857 15.429 0 26.286-10.857l310.286-310.286q10.286-10.286 10.286-25.714zM877.714 512q0 119.429-58.857 220.286t-159.714 159.714-220.286 58.857-220.286-58.857-159.714-159.714-58.857-220.286 58.857-220.286 159.714-159.714 220.286-58.857 220.286 58.857 159.714 159.714 58.857 220.286z"
],
"attrs": [],
"isMulticolor": false,
"tags": [
"check-circle"
],
"defaultCode": 61528,
"grid": 14
},
"attrs": [],
"properties": {
"id": 83,
"order": 9,
"prevSize": 28,
"code": 58886,
"name": "ok-sign"
},
"setIdx": 0,
"iconIdx": 6
},
{
"icon": {
"paths": [
"M658.286 475.429q0-105.714-75.143-180.857t-180.857-75.143-180.857 75.143-75.143 180.857 75.143 180.857 180.857 75.143 180.857-75.143 75.143-180.857zM950.857 950.857q0 29.714-21.714 51.429t-51.429 21.714q-30.857 0-51.429-21.714l-196-195.429q-102.286 70.857-228 70.857-81.714 0-156.286-31.714t-128.571-85.714-85.714-128.571-31.714-156.286 31.714-156.286 85.714-128.571 128.571-85.714 156.286-31.714 156.286 31.714 128.571 85.714 85.714 128.571 31.714 156.286q0 125.714-70.857 228l196 196q21.143 21.143 21.143 51.429z"
],
"width": 951,
"attrs": [],
"isMulticolor": false,
"tags": [
"search"
],
"defaultCode": 61442,
"grid": 14
},
"attrs": [],
"properties": {
"id": 2,
"order": 1,
"prevSize": 28,
"code": 58887,
"name": "icon-search"
},
"setIdx": 0,
"iconIdx": 7
}
],
"height": 1024,
"metadata": {
"name": "slate",
"license": "SIL OFL 1.1"
},
"preferences": {
"showGlyphs": true,
"showQuickUse": true,
"showQuickUse2": true,
"showSVGs": true,
"fontPref": {
"prefix": "icon-",
"metadata": {
"fontFamily": "slate",
"majorVersion": 1,
"minorVersion": 0,
"description": "Based on FontAwesome",
"license": "SIL OFL 1.1"
},
"metrics": {
"emSize": 1024,
"baseline": 6.25,
"whitespace": 50
},
"resetPoint": 58880,
"showSelector": false,
"selector": "class",
"classSelector": ".icon",
"showMetrics": false,
"showMetadata": true,
"showVersion": true,
"ie7": false
},
"imagePref": {
"prefix": "icon-",
"png": true,
"useClassSelector": true,
"color": 4473924,
"bgColor": 16777215
},
"historySize": 100,
"showCodes": true,
"gridSize": 16,
"showLiga": false
}
}

View File

@ -1,95 +0,0 @@
# -*- coding: utf-8 -*- #
# frozen_string_literal: true
# this is based on https://github.com/rouge-ruby/rouge/blob/master/lib/rouge/themes/monokai_sublime.rb
# but without the added background, and changed styling for JSON keys to be soft_yellow instead of white
module Rouge
module Themes
class MonokaiSublimeSlate < CSSTheme
name 'monokai.sublime.slate'
palette :black => '#000000'
palette :bright_green => '#a6e22e'
palette :bright_pink => '#f92672'
palette :carmine => '#960050'
palette :dark => '#49483e'
palette :dark_grey => '#888888'
palette :dark_red => '#aa0000'
palette :dimgrey => '#75715e'
palette :emperor => '#555555'
palette :grey => '#999999'
palette :light_grey => '#aaaaaa'
palette :light_violet => '#ae81ff'
palette :soft_cyan => '#66d9ef'
palette :soft_yellow => '#e6db74'
palette :very_dark => '#1e0010'
palette :whitish => '#f8f8f2'
palette :orange => '#f6aa11'
palette :white => '#ffffff'
style Generic::Heading, :fg => :grey
style Literal::String::Regex, :fg => :orange
style Generic::Output, :fg => :dark_grey
style Generic::Prompt, :fg => :emperor
style Generic::Strong, :bold => false
style Generic::Subheading, :fg => :light_grey
style Name::Builtin, :fg => :orange
style Comment::Multiline,
Comment::Preproc,
Comment::Single,
Comment::Special,
Comment, :fg => :dimgrey
style Error,
Generic::Error,
Generic::Traceback, :fg => :carmine
style Generic::Deleted,
Generic::Inserted,
Generic::Emph, :fg => :dark
style Keyword::Constant,
Keyword::Declaration,
Keyword::Reserved,
Name::Constant,
Keyword::Type, :fg => :soft_cyan
style Literal::Number::Float,
Literal::Number::Hex,
Literal::Number::Integer::Long,
Literal::Number::Integer,
Literal::Number::Oct,
Literal::Number,
Literal::String::Char,
Literal::String::Escape,
Literal::String::Symbol, :fg => :light_violet
style Literal::String::Doc,
Literal::String::Double,
Literal::String::Backtick,
Literal::String::Heredoc,
Literal::String::Interpol,
Literal::String::Other,
Literal::String::Single,
Literal::String, :fg => :soft_yellow
style Name::Attribute,
Name::Class,
Name::Decorator,
Name::Exception,
Name::Function, :fg => :bright_green
style Name::Variable::Class,
Name::Namespace,
Name::Entity,
Name::Builtin::Pseudo,
Name::Variable::Global,
Name::Variable::Instance,
Name::Variable,
Text::Whitespace,
Text,
Name, :fg => :white
style Name::Label, :fg => :bright_pink
style Operator::Word,
Name::Tag,
Keyword,
Keyword::Namespace,
Keyword::Pseudo,
Operator, :fg => :bright_pink
end
end
end

View File

@ -1,16 +0,0 @@
module Multilang
def block_code(code, full_lang_name)
if full_lang_name
parts = full_lang_name.split('--')
rouge_lang_name = (parts) ? parts[0] : "" # just parts[0] here causes null ref exception when no language specified
super(code, rouge_lang_name).sub("highlight #{rouge_lang_name}") do |match|
match + " tab-" + full_lang_name
end
else
super(code, full_lang_name)
end
end
end
require 'middleman-core/renderers/redcarpet'
Middleman::Renderers::MiddlemanRedcarpetHTML.send :include, Multilang

View File

@ -1,22 +0,0 @@
# Nested unique header generation
require 'middleman-core/renderers/redcarpet'
class NestingUniqueHeadCounter < Middleman::Renderers::MiddlemanRedcarpetHTML
def initialize
super
@@headers_history = {} if !defined?(@@headers_history)
end
def header(text, header_level)
friendly_text = text.gsub(/<[^>]*>/,"").parameterize
@@headers_history[header_level] = text.parameterize
if header_level > 1
for i in (header_level - 1).downto(1)
friendly_text.prepend("#{@@headers_history[i]}-") if @@headers_history.key?(i)
end
end
return "<h#{header_level} id='#{friendly_text}'>#{text}</h#{header_level}>"
end
end

View File

@ -1,31 +0,0 @@
require 'nokogiri'
def toc_data(page_content)
html_doc = Nokogiri::HTML::DocumentFragment.parse(page_content)
# get a flat list of headers
headers = []
html_doc.css('h1, h2, h3').each do |header|
headers.push({
id: header.attribute('id').to_s,
content: header.children,
title: header.children.to_s.gsub(/<[^>]*>/, ''),
level: header.name[1].to_i,
children: []
})
end
[3,2].each do |header_level|
header_to_nest = nil
headers = headers.reject do |header|
if header[:level] == header_level
header_to_nest[:children].push header if header_to_nest
true
else
header_to_nest = header if header[:level] < header_level
false
end
end
end
headers
end

View File

@ -1,24 +0,0 @@
# Unique header generation
require 'middleman-core/renderers/redcarpet'
require 'digest'
class UniqueHeadCounter < Middleman::Renderers::MiddlemanRedcarpetHTML
def initialize
super
@head_count = {}
end
def header(text, header_level)
friendly_text = text.gsub(/<[^>]*>/,"").parameterize
if friendly_text.strip.length == 0
# Looks like parameterize removed the whole thing! It removes many unicode
# characters like Chinese and Russian. To get a unique URL, let's just
# URI escape the whole header
friendly_text = Digest::SHA1.hexdigest(text)[0,10]
end
@head_count[friendly_text] ||= 0
@head_count[friendly_text] += 1
if @head_count[friendly_text] > 1
friendly_text += "-#{@head_count[friendly_text]}"
end
return "<h#{header_level} id='#{friendly_text}'>#{text}</h#{header_level}>"
end
end

View File

@ -1,248 +0,0 @@
#!/usr/bin/env bash
set -o errexit #abort if any command fails
me=$(basename "$0")
help_message="\
Usage: $me [<options>] <command> [<command-options>]
Run commands related to the slate process.
Commands:
serve Run the middleman server process, useful for
development.
build Run the build process.
deploy Will build and deploy files to branch. Use
--no-build to only deploy.
Global Options:
-h, --help Show this help information.
-v, --verbose Increase verbosity. Useful for debugging.
Deploy options:
-e, --allow-empty Allow deployment of an empty directory.
-m, --message MESSAGE Specify the message used when committing on the
deploy branch.
-n, --no-hash Don't append the source commit's hash to the deploy
commit's message.
--no-build Do not build the source files.
"
run_serve() {
exec bundle exec middleman serve --watcher-force-polling
}
run_build() {
bundle exec middleman build --clean
}
parse_args() {
# Set args from a local environment file.
if [ -e ".env" ]; then
source .env
fi
command=
# Parse arg flags
# If something is exposed as an environment variable, set/overwrite it
# here. Otherwise, set/overwrite the internal variable instead.
while : ; do
if [[ $1 = "-h" || $1 = "--help" ]]; then
echo "$help_message"
exit 0
elif [[ $1 = "-v" || $1 = "--verbose" ]]; then
verbose=true
shift
elif [[ $1 = "-e" || $1 = "--allow-empty" ]]; then
allow_empty=true
shift
elif [[ ( $1 = "-m" || $1 = "--message" ) && -n $2 ]]; then
commit_message=$2
shift 2
elif [[ $1 = "-n" || $1 = "--no-hash" ]]; then
GIT_DEPLOY_APPEND_HASH=false
shift
elif [[ $1 = "--no-build" ]]; then
no_build=true
shift
elif [[ $1 = "serve" || $1 = "build" || $1 = "deploy" ]]; then
if [ ! -z "${command}" ]; then
>&2 echo "You can only specify one command."
exit 1
fi
command=$1
shift
elif [ -z $1 ]; then
break
fi
done
if [ -z "${command}" ]; then
>&2 echo "Command not specified."
exit 1
fi
# Set internal option vars from the environment and arg flags. All internal
# vars should be declared here, with sane defaults if applicable.
# Source directory & target branch.
deploy_directory=build
deploy_branch=gh-pages
#if no user identity is already set in the current git environment, use this:
default_username=${GIT_DEPLOY_USERNAME:-deploy.sh}
default_email=${GIT_DEPLOY_EMAIL:-}
#repository to deploy to. must be readable and writable.
repo=origin
#append commit hash to the end of message by default
append_hash=${GIT_DEPLOY_APPEND_HASH:-true}
}
main() {
enable_expanded_output
if ! git diff --exit-code --quiet --cached; then
echo Aborting due to uncommitted changes in the index >&2
return 1
fi
commit_title=`git log -n 1 --format="%s" HEAD`
commit_hash=` git log -n 1 --format="%H" HEAD`
#default commit message uses last title if a custom one is not supplied
if [[ -z $commit_message ]]; then
commit_message="publish: $commit_title"
fi
#append hash to commit message unless no hash flag was found
if [ $append_hash = true ]; then
commit_message="$commit_message"$'\n\n'"generated from commit $commit_hash"
fi
previous_branch=`git rev-parse --abbrev-ref HEAD`
if [ ! -d "$deploy_directory" ]; then
echo "Deploy directory '$deploy_directory' does not exist. Aborting." >&2
return 1
fi
# must use short form of flag in ls for compatibility with macOS and BSD
if [[ -z `ls -A "$deploy_directory" 2> /dev/null` && -z $allow_empty ]]; then
echo "Deploy directory '$deploy_directory' is empty. Aborting. If you're sure you want to deploy an empty tree, use the --allow-empty / -e flag." >&2
return 1
fi
if git ls-remote --exit-code $repo "refs/heads/$deploy_branch" ; then
# deploy_branch exists in $repo; make sure we have the latest version
disable_expanded_output
git fetch --force $repo $deploy_branch:$deploy_branch
enable_expanded_output
fi
# check if deploy_branch exists locally
if git show-ref --verify --quiet "refs/heads/$deploy_branch"
then incremental_deploy
else initial_deploy
fi
restore_head
}
initial_deploy() {
git --work-tree "$deploy_directory" checkout --orphan $deploy_branch
git --work-tree "$deploy_directory" add --all
commit+push
}
incremental_deploy() {
#make deploy_branch the current branch
git symbolic-ref HEAD refs/heads/$deploy_branch
#put the previously committed contents of deploy_branch into the index
git --work-tree "$deploy_directory" reset --mixed --quiet
git --work-tree "$deploy_directory" add --all
set +o errexit
diff=$(git --work-tree "$deploy_directory" diff --exit-code --quiet HEAD --)$?
set -o errexit
case $diff in
0) echo No changes to files in $deploy_directory. Skipping commit.;;
1) commit+push;;
*)
echo git diff exited with code $diff. Aborting. Staying on branch $deploy_branch so you can debug. To switch back to main, use: git symbolic-ref HEAD refs/heads/main && git reset --mixed >&2
return $diff
;;
esac
}
commit+push() {
set_user_id
git --work-tree "$deploy_directory" commit -m "$commit_message"
disable_expanded_output
#--quiet is important here to avoid outputting the repo URL, which may contain a secret token
git push --quiet $repo $deploy_branch
enable_expanded_output
}
#echo expanded commands as they are executed (for debugging)
enable_expanded_output() {
if [ $verbose ]; then
set -o xtrace
set +o verbose
fi
}
#this is used to avoid outputting the repo URL, which may contain a secret token
disable_expanded_output() {
if [ $verbose ]; then
set +o xtrace
set -o verbose
fi
}
set_user_id() {
if [[ -z `git config user.name` ]]; then
git config user.name "$default_username"
fi
if [[ -z `git config user.email` ]]; then
git config user.email "$default_email"
fi
}
restore_head() {
if [[ $previous_branch = "HEAD" ]]; then
#we weren't on any branch before, so just set HEAD back to the commit it was on
git update-ref --no-deref HEAD $commit_hash $deploy_branch
else
git symbolic-ref HEAD refs/heads/$previous_branch
fi
git reset --mixed
}
filter() {
sed -e "s|$repo|\$repo|g"
}
sanitize() {
"$@" 2> >(filter 1>&2) | filter
}
parse_args "$@"
if [ "${command}" = "serve" ]; then
run_serve
elif [[ "${command}" = "build" ]]; then
run_build
elif [[ ${command} = "deploy" ]]; then
if [[ ${no_build} != true ]]; then
run_build
fi
main "$@"
fi

Binary file not shown.

View File

@ -1,14 +0,0 @@
<?xml version="1.0" standalone="no"?>
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd" >
<svg xmlns="http://www.w3.org/2000/svg">
<metadata>Generated by IcoMoon</metadata>
<defs>
<font id="slate" horiz-adv-x="1024">
<font-face units-per-em="1024" ascent="960" descent="-64" />
<missing-glyph horiz-adv-x="1024" />
<glyph unicode="&#x20;" d="" horiz-adv-x="512" />
<glyph unicode="&#xe600;" d="M438.857 877.714q119.429 0 220.286-58.857t159.714-159.714 58.857-220.286-58.857-220.286-159.714-159.714-220.286-58.857-220.286 58.857-159.714 159.714-58.857 220.286 58.857 220.286 159.714 159.714 220.286 58.857zM512 165.143v108.571q0 8-5.143 13.429t-12.571 5.429h-109.714q-7.429 0-13.143-5.714t-5.714-13.143v-108.571q0-7.429 5.714-13.143t13.143-5.714h109.714q7.429 0 12.571 5.429t5.143 13.429zM510.857 361.714l10.286 354.857q0 6.857-5.714 10.286-5.714 4.571-13.714 4.571h-125.714q-8 0-13.714-4.571-5.714-3.429-5.714-10.286l9.714-354.857q0-5.714 5.714-10t13.714-4.286h105.714q8 0 13.429 4.286t6 10z" />
<glyph unicode="&#xe602;" d="M585.143 164.571v91.429q0 8-5.143 13.143t-13.143 5.143h-54.857v292.571q0 8-5.143 13.143t-13.143 5.143h-182.857q-8 0-13.143-5.143t-5.143-13.143v-91.429q0-8 5.143-13.143t13.143-5.143h54.857v-182.857h-54.857q-8 0-13.143-5.143t-5.143-13.143v-91.429q0-8 5.143-13.143t13.143-5.143h256q8 0 13.143 5.143t5.143 13.143zM512 676.571v91.429q0 8-5.143 13.143t-13.143 5.143h-109.714q-8 0-13.143-5.143t-5.143-13.143v-91.429q0-8 5.143-13.143t13.143-5.143h109.714q8 0 13.143 5.143t5.143 13.143zM877.714 438.857q0-119.429-58.857-220.286t-159.714-159.714-220.286-58.857-220.286 58.857-159.714 159.714-58.857 220.286 58.857 220.286 159.714 159.714 220.286 58.857 220.286-58.857 159.714-159.714 58.857-220.286z" />
<glyph unicode="&#xe606;" d="M733.714 531.428q0 16-10.286 26.286l-52 51.429q-10.857 10.857-25.714 10.857t-25.714-10.857l-233.143-232.571-129.143 129.143q-10.857 10.857-25.714 10.857t-25.714-10.857l-52-51.429q-10.286-10.286-10.286-26.286 0-15.429 10.286-25.714l206.857-206.857q10.857-10.857 25.714-10.857 15.429 0 26.286 10.857l310.286 310.286q10.286 10.286 10.286 25.714zM877.714 438.857q0-119.429-58.857-220.286t-159.714-159.714-220.286-58.857-220.286 58.857-159.714 159.714-58.857 220.286 58.857 220.286 159.714 159.714 220.286 58.857 220.286-58.857 159.714-159.714 58.857-220.286z" />
<glyph unicode="&#xe607;" d="M658.286 475.428q0 105.714-75.143 180.857t-180.857 75.143-180.857-75.143-75.143-180.857 75.143-180.857 180.857-75.143 180.857 75.143 75.143 180.857zM950.857 0q0-29.714-21.714-51.429t-51.429-21.714q-30.857 0-51.429 21.714l-196 195.429q-102.286-70.857-228-70.857-81.714 0-156.286 31.714t-128.571 85.714-85.714 128.571-31.714 156.286 31.714 156.286 85.714 128.571 128.571 85.714 156.286 31.714 156.286-31.714 128.571-85.714 85.714-128.571 31.714-156.286q0-125.714-70.857-228l196-196q21.143-21.143 21.143-51.429z" horiz-adv-x="951" />
</font></defs></svg>

Before

Width:  |  Height:  |  Size: 2.9 KiB

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Before

Width:  |  Height:  |  Size: 96 B

View File

@ -1,78 +0,0 @@
# Jobs
<aside class="notice">
All routes in this section require authentication.
</aside>
## Manually schedule a job
```shell
curl \
-H 'X-Api-Key: secret' \
https://example.com/api/v1/jobs/queue?target=10&force&arch=x86_64
```
Manually schedule a job on the server.
### HTTP Request
`POST /api/v1/jobs/queue`
### Query Parameters
Parameter | Description
--------- | -----------
target | Id of target to schedule build for
arch | Architecture to build on
force | Whether it's a forced build (true if present)
## Poll for new jobs
<aside class="warning">
This endpoint is used by the agents and should not be used manually. It's just
here for completeness. Requests to this endpoint modify the build queue,
meaning manual requests can cause builds to be skipped.
</aside>
```shell
curl \
-H 'X-Api-Key: secret' \
https://example.com/api/v1/jobs/poll?arch=x86_64&max=2
```
> JSON output format
```json
{
"message": "",
"data": [
{
"target_id": 1,
"kind": "git",
"url": "https://aur.archlinux.org/discord-ptb.git",
"branch": "master",
"path": "",
"repo": "bur",
"base_image": "archlinux:base-devel",
"force": true
}
]
}
```
Poll the server for new builds.
### HTTP Request
`GET /api/v1/jobs/poll`
### Query Parameters
Parameter | Description
--------- | -----------
arch | For which architecture to receive jobs
max | How many jobs to receive at most

View File

@ -1,172 +0,0 @@
# Build Logs
<aside class="notice">
All routes in this section require authentication.
</aside>
Endpoints for interacting with stored build logs.
## List logs
```shell
curl \
-H 'X-Api-Key: secret' \
https://example.com/api/v1/logs?offset=10&limit=20
```
> JSON output format
```json
{
"message": "",
"data": [
{
"id": 1,
"target_id": 3,
"start_time": 1652008554,
"end_time": 1652008559,
"arch": "x86_64",
"exit_code": 0
}
]
}
```
Retrieve a list of build logs.
### HTTP Request
`GET /api/v1/logs`
### Query Parameters
Parameter | Description
--------- | -----------
limit | Maximum amount of results to return.
offset | Offset of results.
target | Only return builds for this target id.
before | Only return logs started before this time (UTC epoch)
after | Only return logs started after this time (UTC epoch)
arch | Only return logs built on this architecture
exit_codes | Comma-separated list of exit codes to limit result to; using `!` as a prefix makes it exclude that value. For example, `1,2` only returns logs with status code 1 or 2, while `!1,!2` returns those that don't have 1 or 2 as the result.
## Get build log
```shell
curl \
-H 'X-Api-Key: secret' \
https://example.com/api/v1/logs/1
```
> JSON output format
```json
{
"message": "",
"data": {
"id": 1,
"target_id": 3,
"start_time": 1652008554,
"end_time": 1652008559,
"arch": "x86_64",
"exit_code": 0
}
}
```
Retrieve info about a specific build log.
### HTTP Request
`GET /api/v1/logs/:id`
### URL Parameters
Parameter | Description
--------- | -----------
id | ID of requested log
## Get log contents
```shell
curl \
-H 'X-Api-Key: secret' \
https://example.com/api/v1/logs/15/content
```
Retrieve the contents of a build log. The response is the build log in
plaintext.
### HTTP Request
`GET /api/v1/logs/:id/content`
### URL Parameters
Parameter | Description
--------- | -----------
id | ID of requested log
## Publish build log
> JSON output format
```json
{
"message": "",
"data": {
"id": 15
}
}
```
<aside class="warning">
This endpoint is used by the agents and should not be used manually unless you
know what you're doing. It's just here for completeness.
</aside>
Publish a new build log to the server.
### HTTP Request
`POST /api/v1/logs`
### Query parameters
Parameter | Description
--------- | -----------
startTime | Start time of the build (UTC epoch)
endTime | End time of the build (UTC epoch)
arch | Architecture on which the build was done
exitCode | Exit code of the build container
target | id of target this build is for
### Request body
Plaintext contents of the build log.
## Remove a build log
```shell
curl \
-XDELETE \
-H 'X-Api-Key: secret' \
https://example.com/api/v1/logs/1
```
Remove a build log from the server.
### HTTP Request
`DELETE /api/v1/logs/:id`
### URL Parameters
Parameter | Description
--------- | -----------
id | id of log to remove

View File

@ -1,179 +0,0 @@
# Repository
Besides providing a RESTful API, the Vieter server is also a Pacman-compatible
repository server. This section describes the various routes that make this
possible.
## Get a package archive or database file
```shell
curl -L https://example.com/bur/x86_64/tuxedo-keyboard-3.0.10-1-x86_64.pkg.tar.zst
```
This endpoint is really the entire repository. It serves both the package
archives & the database files for a specific arch-repo. It has three different
behaviors, depending on `filename`:
* If the file extension is one of `.db`, `.files`, `.db.tar.gz` or
`.files.tar.gz`, it tries to serve the requested database file.
* If the filename contains `.pkg`, it serves the package file.
* Otherwise, it assumes `filename` is the name & version of a package inside
the repository (e.g. `vieter-0.3.0_alpha.2-1`) & serves that package's `desc`
file from inside the database archive.
<aside class="notice">
The final option might sound a little strange, but it's used by the build
system to determine whether a package needs to be rebuilt.
</aside>
### HTTP Request
`GET /:repo/:arch/:filename`
### URL Parameters
Parameter | Description
--------- | -----------
repo | Repository containing the package
arch | Arch-repo containing the package
filename | actual filename to request
## Check whether file exists
```shell
curl -L https://example.com/bur/x86_64/tuxedo-keyboard-3.0.10-1-x86_64.pkg.tar.zst
```
The above request can also be performed as a HEAD request. The behavior is the
same, except no data is returned besides an error 404 if the file doesn't exist
& an error 200 otherwise.
### HTTP Request
`GET /:repo/:arch/:filename`
### URL Parameters
Parameter | Description
--------- | -----------
repo | Repository containing the package
arch | Arch-repo containing the package
filename | actual filename to request
## Publish package
<aside class="notice">
This endpoint requests authentication.
</aside>
```shell
curl \
-H 'X-Api-Key: secret' \
-XPOST \
-T tuxedo-keyboard-3.0.10-1-x86_64.pkg.tar.zst \
https://example.com/some-repo/publish
```
This endpoint allows you to publish a new package archive to a given repo.
If the package's architecture is not `any`, it is added to that specific
arch-repo. Otherwise, it is added to the configured default architecture & any
other already present arch-repos.
### HTTP Request
`POST /:repo/publish`
### URL Parameters
Parameter | Description
--------- | -----------
repo | Repository to publish package to
## Remove package from arch-repo
<aside class="notice">
This endpoint requests authentication.
</aside>
```shell
curl \
-H 'X-Api-Key: secret' \
-XDELETE \
https://example.com/vieter/x86_64/mike
```
This endpoint allows you to remove a package from a given arch-repo.
### HTTP Request
`DELETE /:repo/:arch/:pkg`
### URL Parameters
Parameter | Description
--------- | -----------
repo | Repository to delete package from
arch | Specific arch-repo to remove package from
pkg | Name of package to remove (without any version information)
## Remove arch-repo
<aside class="notice">
This endpoint requests authentication.
</aside>
```shell
curl \
-H 'X-Api-Key: secret' \
-XDELETE \
https://example.com/vieter/x86_64
```
This endpoint allows removing an entire arch-repo.
### HTTP Request
`DELETE /:repo/:arch`
### URL Parameters
Parameter | Description
--------- | -----------
repo | Repository to delete arch-repo from
arch | Specific architecture to remove
## Remove repo
<aside class="notice">
This endpoint requests authentication.
</aside>
```shell
curl \
-H 'X-Api-Key: secret' \
-XDELETE \
https://example.com/vieter
```
This endpoint allows removing an entire repo.
### HTTP Request
`DELETE /:repo`
### URL Parameters
Parameter | Description
--------- | -----------
repo | Repository to delete

View File

@ -1,181 +0,0 @@
# Targets
<aside class="notice">
All routes in this section require authentication.
</aside>
Endpoints for interacting with the list of targets stored on the server.
## List targets
```shell
curl \
-H 'X-Api-Key: secret' \
https://example.com/api/v1/targets?offset=10&limit=20
```
> JSON output format
```json
{
"message": "",
"data": [
{
"id": 1,
"kind": "git",
"url": "https://aur.archlinux.org/discord-ptb.git",
"branch": "master",
"path" : "",
"repo": "bur",
"schedule": "",
"arch": [
{
"id": 1,
"target_id": 1,
"value": "x86_64"
}
]
}
]
}
```
Retrieve a list of targets.
### HTTP Request
`GET /api/v1/targets`
### Query Parameters
Parameter | Description
--------- | -----------
limit | Maximum amount of results to return.
offset | Offset of results.
repo | Limit results to targets that publish to the given repo.
query | Only return targets that have this substring in their URL, path or branch.
arch | Only return targets that publish to this arch.
## Get specific target
```shell
curl \
-H 'X-Api-Key: secret' \
https://example.com/api/v1/targets/1
```
> JSON output format
```json
{
"message": "",
"data": {
"id": 1,
"kind": "git",
"url": "https://aur.archlinux.org/discord-ptb.git",
"branch": "master",
"path": "",
"repo": "bur",
"schedule": "0 2",
"arch": [
{
"id": 1,
"target_id": 1,
"value": "x86_64"
}
]
}
}
```
Get info about a specific target.
### HTTP Request
`GET /api/v1/targets/:id`
### URL Parameters
Parameter | Description
--------- | -----------
id | id of requested target
## Create a new target
> JSON output format
```json
{
"message": "",
"data": {
"id": 15
}
}
```
Create a new target with the given data.
### HTTP Request
`POST /api/v1/targets`
### Query Parameters
Parameter | Description
--------- | -----------
kind | Kind of target to add; one of 'git', 'url'.
url | URL of the Git repository.
branch | Branch of the Git repository.
path | Subdirectory inside Git repository to use.
repo | Vieter repository to publish built packages to.
schedule | Cron build schedule (syntax explained [here](https://rustybever.be/docs/vieter/usage/builds/schedule/))
arch | Comma-separated list of architectures to build package on.
## Modify a target
Modify the data of an existing target.
### HTTP Request
`PATCH /api/v1/targets/:id`
### URL Parameters
Parameter | Description
--------- | -----------
id | id of target to modify
### Query Parameters
Parameter | Description
--------- | -----------
kind | Kind of target; one of 'git', 'url'.
url | URL of the Git repository.
branch | Branch of the Git repository.
path | Subdirectory inside Git repository to use.
repo | Vieter repository to publish built packages to.
schedule | Cron build schedule
arch | Comma-separated list of architectures to build package on.
## Remove a target
```shell
curl \
-XDELETE \
-H 'X-Api-Key: secret' \
https://example.com/api/v1/targets/1
```
Remove a target from the server.
### HTTP Request
`DELETE /api/v1/targets/:id`
### URL Parameters
Parameter | Description
--------- | -----------
id | id of target to remove

View File

@ -1,40 +0,0 @@
---
title: API Reference
language_tabs: # must be one of https://git.io/vQNgJ
- shell: cURL
toc_footers:
- <a href='https://github.com/slatedocs/slate'>Documentation Powered by Slate</a>
includes:
- repository
- targets
- logs
- jobs
search: true
code_clipboard: true
meta:
- name: description
content: Documentation for the Vieter API
---
# Introduction
Welcome to the Vieter API documentation! Here, you can find everything related
to interacting with Vieter's HTTP API.
# Authentication
```shell
curl -H 'X-Api-Key: secret' https://example.com/api/some/path
```
> Don't forget to replace `secret` with your Vieter instance's secret.
Authentication is done by passing the HTTP header `X-Api-Key: secret` along
with each request, where `secret` is replaced with your Vieter server's
configured secret.

View File

@ -1,2 +0,0 @@
//= require ./all_nosearch
//= require ./app/_search

View File

@ -1,27 +0,0 @@
//= require ./lib/_energize
//= require ./app/_copy
//= require ./app/_toc
//= require ./app/_lang
function adjustLanguageSelectorWidth() {
const elem = $('.dark-box > .lang-selector');
elem.width(elem.parent().width());
}
$(function() {
loadToc($('#toc'), '.toc-link', '.toc-list-h2', 10);
setupLanguages($('body').data('languages'));
$('.content').imagesLoaded( function() {
window.recacheHeights();
window.refreshToc();
});
$(window).resize(function() {
adjustLanguageSelectorWidth();
});
adjustLanguageSelectorWidth();
});
window.onpopstate = function() {
activateLanguage(getLanguageFromQueryString());
};

View File

@ -1,15 +0,0 @@
function copyToClipboard(container) {
const el = document.createElement('textarea');
el.value = container.textContent.replace(/\n$/, '');
document.body.appendChild(el);
el.select();
document.execCommand('copy');
document.body.removeChild(el);
}
function setupCodeCopy() {
$('pre.highlight').prepend('<div class="copy-clipboard"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><title>Copy to Clipboard</title><path d="M18 6v-6h-18v18h6v6h18v-18h-6zm-12 10h-4v-14h14v4h-10v10zm16 6h-14v-14h14v14z"></path></svg></div>');
$('.copy-clipboard').on('click', function() {
copyToClipboard(this.parentNode.children[1]);
});
}

View File

@ -1,171 +0,0 @@
//= require ../lib/_jquery
/*
Copyright 2008-2013 Concur Technologies, Inc.
Licensed under the Apache License, Version 2.0 (the "License"); you may
not use this file except in compliance with the License. You may obtain
a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
License for the specific language governing permissions and limitations
under the License.
*/
;(function () {
'use strict';
var languages = [];
window.setupLanguages = setupLanguages;
window.activateLanguage = activateLanguage;
window.getLanguageFromQueryString = getLanguageFromQueryString;
function activateLanguage(language) {
if (!language) return;
if (language === "") return;
$(".lang-selector a").removeClass('active');
$(".lang-selector a[data-language-name='" + language + "']").addClass('active');
for (var i=0; i < languages.length; i++) {
$(".highlight.tab-" + languages[i]).hide();
$(".lang-specific." + languages[i]).hide();
}
$(".highlight.tab-" + language).show();
$(".lang-specific." + language).show();
window.recacheHeights();
// scroll to the new location of the position
if ($(window.location.hash).get(0)) {
$(window.location.hash).get(0).scrollIntoView(true);
}
}
// parseURL and stringifyURL are from https://github.com/sindresorhus/query-string
// MIT licensed
// https://github.com/sindresorhus/query-string/blob/7bee64c16f2da1a326579e96977b9227bf6da9e6/license
function parseURL(str) {
if (typeof str !== 'string') {
return {};
}
str = str.trim().replace(/^(\?|#|&)/, '');
if (!str) {
return {};
}
return str.split('&').reduce(function (ret, param) {
var parts = param.replace(/\+/g, ' ').split('=');
var key = parts[0];
var val = parts[1];
key = decodeURIComponent(key);
// missing `=` should be `null`:
// http://w3.org/TR/2012/WD-url-20120524/#collect-url-parameters
val = val === undefined ? null : decodeURIComponent(val);
if (!ret.hasOwnProperty(key)) {
ret[key] = val;
} else if (Array.isArray(ret[key])) {
ret[key].push(val);
} else {
ret[key] = [ret[key], val];
}
return ret;
}, {});
};
function stringifyURL(obj) {
return obj ? Object.keys(obj).sort().map(function (key) {
var val = obj[key];
if (Array.isArray(val)) {
return val.sort().map(function (val2) {
return encodeURIComponent(key) + '=' + encodeURIComponent(val2);
}).join('&');
}
return encodeURIComponent(key) + '=' + encodeURIComponent(val);
}).join('&') : '';
};
// gets the language set in the query string
function getLanguageFromQueryString() {
if (location.search.length >= 1) {
var language = parseURL(location.search).language;
if (language) {
return language;
} else if (jQuery.inArray(location.search.substr(1), languages) != -1) {
return location.search.substr(1);
}
}
return false;
}
// returns a new query string with the new language in it
function generateNewQueryString(language) {
var url = parseURL(location.search);
if (url.language) {
url.language = language;
return stringifyURL(url);
}
return language;
}
// if a button is clicked, add the state to the history
function pushURL(language) {
if (!history) { return; }
var hash = window.location.hash;
if (hash) {
hash = hash.replace(/^#+/, '');
}
history.pushState({}, '', '?' + generateNewQueryString(language) + '#' + hash);
// save language as next default
if (localStorage) {
localStorage.setItem("language", language);
}
}
function setupLanguages(l) {
var defaultLanguage = null;
if (localStorage) {
defaultLanguage = localStorage.getItem("language");
}
languages = l;
var presetLanguage = getLanguageFromQueryString();
if (presetLanguage) {
// the language is in the URL, so use that language!
activateLanguage(presetLanguage);
if (localStorage) {
localStorage.setItem("language", presetLanguage);
}
} else if ((defaultLanguage !== null) && (jQuery.inArray(defaultLanguage, languages) != -1)) {
// the language was the last selected one saved in localstorage, so use that language!
activateLanguage(defaultLanguage);
} else {
// no language selected, so use the default
activateLanguage(languages[0]);
}
}
// if we click on a language tab, activate that language
$(function() {
$(".lang-selector a").on("click", function() {
var language = $(this).data("language-name");
pushURL(language);
activateLanguage(language);
return false;
});
});
})();

View File

@ -1,102 +0,0 @@
//= require ../lib/_lunr
//= require ../lib/_jquery
//= require ../lib/_jquery.highlight
;(function () {
'use strict';
var content, searchResults;
var highlightOpts = { element: 'span', className: 'search-highlight' };
var searchDelay = 0;
var timeoutHandle = 0;
var index;
function populate() {
index = lunr(function(){
this.ref('id');
this.field('title', { boost: 10 });
this.field('body');
this.pipeline.add(lunr.trimmer, lunr.stopWordFilter);
var lunrConfig = this;
$('h1, h2').each(function() {
var title = $(this);
var body = title.nextUntil('h1, h2');
lunrConfig.add({
id: title.prop('id'),
title: title.text(),
body: body.text()
});
});
});
determineSearchDelay();
}
$(populate);
$(bind);
function determineSearchDelay() {
if (index.tokenSet.toArray().length>5000) {
searchDelay = 300;
}
}
function bind() {
content = $('.content');
searchResults = $('.search-results');
$('#input-search').on('keyup',function(e) {
var wait = function() {
return function(executingFunction, waitTime){
clearTimeout(timeoutHandle);
timeoutHandle = setTimeout(executingFunction, waitTime);
};
}();
wait(function(){
search(e);
}, searchDelay);
});
}
function search(event) {
var searchInput = $('#input-search')[0];
unhighlight();
searchResults.addClass('visible');
// ESC clears the field
if (event.keyCode === 27) searchInput.value = '';
if (searchInput.value) {
var results = index.search(searchInput.value).filter(function(r) {
return r.score > 0.0001;
});
if (results.length) {
searchResults.empty();
$.each(results, function (index, result) {
var elem = document.getElementById(result.ref);
searchResults.append("<li><a href='#" + result.ref + "'>" + $(elem).text() + "</a></li>");
});
highlight.call(searchInput);
} else {
searchResults.html('<li></li>');
$('.search-results li').text('No Results Found for "' + searchInput.value + '"');
}
} else {
unhighlight();
searchResults.removeClass('visible');
}
}
function highlight() {
if (this.value) content.highlight(this.value, highlightOpts);
}
function unhighlight() {
content.unhighlight(highlightOpts);
}
})();

View File

@ -1,122 +0,0 @@
//= require ../lib/_jquery
//= require ../lib/_imagesloaded.min
;(function () {
'use strict';
var htmlPattern = /<[^>]*>/g;
var loaded = false;
var debounce = function(func, waitTime) {
var timeout = false;
return function() {
if (timeout === false) {
setTimeout(function() {
func();
timeout = false;
}, waitTime);
timeout = true;
}
};
};
var closeToc = function() {
$(".toc-wrapper").removeClass('open');
$("#nav-button").removeClass('open');
};
function loadToc($toc, tocLinkSelector, tocListSelector, scrollOffset) {
var headerHeights = {};
var pageHeight = 0;
var windowHeight = 0;
var originalTitle = document.title;
var recacheHeights = function() {
headerHeights = {};
pageHeight = $(document).height();
windowHeight = $(window).height();
$toc.find(tocLinkSelector).each(function() {
var targetId = $(this).attr('href');
if (targetId[0] === "#") {
headerHeights[targetId] = $("#" + $.escapeSelector(targetId.substring(1))).offset().top;
}
});
};
var refreshToc = function() {
var currentTop = $(document).scrollTop() + scrollOffset;
if (currentTop + windowHeight >= pageHeight) {
// at bottom of page, so just select last header by making currentTop very large
// this fixes the problem where the last header won't ever show as active if its content
// is shorter than the window height
currentTop = pageHeight + 1000;
}
var best = null;
for (var name in headerHeights) {
if ((headerHeights[name] < currentTop && headerHeights[name] > headerHeights[best]) || best === null) {
best = name;
}
}
// Catch the initial load case
if (currentTop == scrollOffset && !loaded) {
best = window.location.hash;
loaded = true;
}
var $best = $toc.find("[href='" + best + "']").first();
if (!$best.hasClass("active")) {
// .active is applied to the ToC link we're currently on, and its parent <ul>s selected by tocListSelector
// .active-expanded is applied to the ToC links that are parents of this one
$toc.find(".active").removeClass("active");
$toc.find(".active-parent").removeClass("active-parent");
$best.addClass("active");
$best.parents(tocListSelector).addClass("active").siblings(tocLinkSelector).addClass('active-parent');
$best.siblings(tocListSelector).addClass("active");
$toc.find(tocListSelector).filter(":not(.active)").slideUp(150);
$toc.find(tocListSelector).filter(".active").slideDown(150);
if (window.history.replaceState) {
window.history.replaceState(null, "", best);
}
var thisTitle = $best.data("title");
if (thisTitle !== undefined && thisTitle.length > 0) {
document.title = thisTitle.replace(htmlPattern, "") + " " + originalTitle;
} else {
document.title = originalTitle;
}
}
};
var makeToc = function() {
recacheHeights();
refreshToc();
$("#nav-button").click(function() {
$(".toc-wrapper").toggleClass('open');
$("#nav-button").toggleClass('open');
return false;
});
$(".page-wrapper").click(closeToc);
$(".toc-link").click(closeToc);
// reload immediately after scrolling on toc click
$toc.find(tocLinkSelector).click(function() {
setTimeout(function() {
refreshToc();
}, 0);
});
$(window).scroll(debounce(refreshToc, 200));
$(window).resize(debounce(recacheHeights, 200));
};
makeToc();
window.recacheHeights = recacheHeights;
window.refreshToc = refreshToc;
}
window.loadToc = loadToc;
})();

View File

@ -1,169 +0,0 @@
/**
* energize.js v0.1.0
*
* Speeds up click events on mobile devices.
* https://github.com/davidcalhoun/energize.js
*/
(function() { // Sandbox
/**
* Don't add to non-touch devices, which don't need to be sped up
*/
if(!('ontouchstart' in window)) return;
var lastClick = {},
isThresholdReached, touchstart, touchmove, touchend,
click, closest;
/**
* isThresholdReached
*
* Compare touchstart with touchend xy coordinates,
* and only fire simulated click event if the coordinates
* are nearby. (don't want clicking to be confused with a swipe)
*/
isThresholdReached = function(startXY, xy) {
return Math.abs(startXY[0] - xy[0]) > 5 || Math.abs(startXY[1] - xy[1]) > 5;
};
/**
* touchstart
*
* Save xy coordinates when the user starts touching the screen
*/
touchstart = function(e) {
this.startXY = [e.touches[0].clientX, e.touches[0].clientY];
this.threshold = false;
};
/**
* touchmove
*
* Check if the user is scrolling past the threshold.
* Have to check here because touchend will not always fire
* on some tested devices (Kindle Fire?)
*/
touchmove = function(e) {
// NOOP if the threshold has already been reached
if(this.threshold) return false;
this.threshold = isThresholdReached(this.startXY, [e.touches[0].clientX, e.touches[0].clientY]);
};
/**
* touchend
*
* If the user didn't scroll past the threshold between
* touchstart and touchend, fire a simulated click.
*
* (This will fire before a native click)
*/
touchend = function(e) {
// Don't fire a click if the user scrolled past the threshold
if(this.threshold || isThresholdReached(this.startXY, [e.changedTouches[0].clientX, e.changedTouches[0].clientY])) {
return;
}
/**
* Create and fire a click event on the target element
* https://developer.mozilla.org/en/DOM/event.initMouseEvent
*/
var touch = e.changedTouches[0],
evt = document.createEvent('MouseEvents');
evt.initMouseEvent('click', true, true, window, 0, touch.screenX, touch.screenY, touch.clientX, touch.clientY, false, false, false, false, 0, null);
evt.simulated = true; // distinguish from a normal (nonsimulated) click
e.target.dispatchEvent(evt);
};
/**
* click
*
* Because we've already fired a click event in touchend,
* we need to listed for all native click events here
* and suppress them as necessary.
*/
click = function(e) {
/**
* Prevent ghost clicks by only allowing clicks we created
* in the click event we fired (look for e.simulated)
*/
var time = Date.now(),
timeDiff = time - lastClick.time,
x = e.clientX,
y = e.clientY,
xyDiff = [Math.abs(lastClick.x - x), Math.abs(lastClick.y - y)],
target = closest(e.target, 'A') || e.target, // needed for standalone apps
nodeName = target.nodeName,
isLink = nodeName === 'A',
standAlone = window.navigator.standalone && isLink && e.target.getAttribute("href");
lastClick.time = time;
lastClick.x = x;
lastClick.y = y;
/**
* Unfortunately Android sometimes fires click events without touch events (seen on Kindle Fire),
* so we have to add more logic to determine the time of the last click. Not perfect...
*
* Older, simpler check: if((!e.simulated) || standAlone)
*/
if((!e.simulated && (timeDiff < 500 || (timeDiff < 1500 && xyDiff[0] < 50 && xyDiff[1] < 50))) || standAlone) {
e.preventDefault();
e.stopPropagation();
if(!standAlone) return false;
}
/**
* Special logic for standalone web apps
* See http://stackoverflow.com/questions/2898740/iphone-safari-web-app-opens-links-in-new-window
*/
if(standAlone) {
window.location = target.getAttribute("href");
}
/**
* Add an energize-focus class to the targeted link (mimics :focus behavior)
* TODO: test and/or remove? Does this work?
*/
if(!target || !target.classList) return;
target.classList.add("energize-focus");
window.setTimeout(function(){
target.classList.remove("energize-focus");
}, 150);
};
/**
* closest
* @param {HTMLElement} node current node to start searching from.
* @param {string} tagName the (uppercase) name of the tag you're looking for.
*
* Find the closest ancestor tag of a given node.
*
* Starts at node and goes up the DOM tree looking for a
* matching nodeName, continuing until hitting document.body
*/
closest = function(node, tagName){
var curNode = node;
while(curNode !== document.body) { // go up the dom until we find the tag we're after
if(!curNode || curNode.nodeName === tagName) { return curNode; } // found
curNode = curNode.parentNode; // not found, so keep going up
}
return null; // not found
};
/**
* Add all delegated event listeners
*
* All the events we care about bubble up to document,
* so we can take advantage of event delegation.
*
* Note: no need to wait for DOMContentLoaded here
*/
document.addEventListener('touchstart', touchstart, false);
document.addEventListener('touchmove', touchmove, false);
document.addEventListener('touchend', touchend, false);
document.addEventListener('click', click, true); // TODO: why does this use capture?
})();

File diff suppressed because one or more lines are too long

View File

@ -1,108 +0,0 @@
/*
* jQuery Highlight plugin
*
* Based on highlight v3 by Johann Burkard
* http://johannburkard.de/blog/programming/javascript/highlight-javascript-text-higlighting-jquery-plugin.html
*
* Code a little bit refactored and cleaned (in my humble opinion).
* Most important changes:
* - has an option to highlight only entire words (wordsOnly - false by default),
* - has an option to be case sensitive (caseSensitive - false by default)
* - highlight element tag and class names can be specified in options
*
* Usage:
* // wrap every occurrance of text 'lorem' in content
* // with <span class='highlight'> (default options)
* $('#content').highlight('lorem');
*
* // search for and highlight more terms at once
* // so you can save some time on traversing DOM
* $('#content').highlight(['lorem', 'ipsum']);
* $('#content').highlight('lorem ipsum');
*
* // search only for entire word 'lorem'
* $('#content').highlight('lorem', { wordsOnly: true });
*
* // don't ignore case during search of term 'lorem'
* $('#content').highlight('lorem', { caseSensitive: true });
*
* // wrap every occurrance of term 'ipsum' in content
* // with <em class='important'>
* $('#content').highlight('ipsum', { element: 'em', className: 'important' });
*
* // remove default highlight
* $('#content').unhighlight();
*
* // remove custom highlight
* $('#content').unhighlight({ element: 'em', className: 'important' });
*
*
* Copyright (c) 2009 Bartek Szopka
*
* Licensed under MIT license.
*
*/
jQuery.extend({
highlight: function (node, re, nodeName, className) {
if (node.nodeType === 3) {
var match = node.data.match(re);
if (match) {
var highlight = document.createElement(nodeName || 'span');
highlight.className = className || 'highlight';
var wordNode = node.splitText(match.index);
wordNode.splitText(match[0].length);
var wordClone = wordNode.cloneNode(true);
highlight.appendChild(wordClone);
wordNode.parentNode.replaceChild(highlight, wordNode);
return 1; //skip added node in parent
}
} else if ((node.nodeType === 1 && node.childNodes) && // only element nodes that have children
!/(script|style)/i.test(node.tagName) && // ignore script and style nodes
!(node.tagName === nodeName.toUpperCase() && node.className === className)) { // skip if already highlighted
for (var i = 0; i < node.childNodes.length; i++) {
i += jQuery.highlight(node.childNodes[i], re, nodeName, className);
}
}
return 0;
}
});
jQuery.fn.unhighlight = function (options) {
var settings = { className: 'highlight', element: 'span' };
jQuery.extend(settings, options);
return this.find(settings.element + "." + settings.className).each(function () {
var parent = this.parentNode;
parent.replaceChild(this.firstChild, this);
parent.normalize();
}).end();
};
jQuery.fn.highlight = function (words, options) {
var settings = { className: 'highlight', element: 'span', caseSensitive: false, wordsOnly: false };
jQuery.extend(settings, options);
if (words.constructor === String) {
words = [words];
}
words = jQuery.grep(words, function(word, i){
return word != '';
});
words = jQuery.map(words, function(word, i) {
return word.replace(/[-[\]{}()*+?.,\\^$|#\s]/g, "\\$&");
});
if (words.length == 0) { return this; };
var flag = settings.caseSensitive ? "" : "i";
var pattern = "(" + words.join("|") + ")";
if (settings.wordsOnly) {
pattern = "\\b" + pattern + "\\b";
}
var re = new RegExp(pattern, flag);
return this.each(function () {
jQuery.highlight(this, re, settings.element, settings.className);
});
};

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@ -1,137 +0,0 @@
<%#
Copyright 2008-2013 Concur Technologies, Inc.
Licensed under the Apache License, Version 2.0 (the "License"); you may
not use this file except in compliance with the License. You may obtain
a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
License for the specific language governing permissions and limitations
under the License.
%>
<% language_tabs = current_page.data.language_tabs || [] %>
<% page_content = yield %>
<%
if current_page.data.includes
current_page.data.includes.each do |include|
page_content += partial("includes/#{include}")
end
end
%>
<!doctype html>
<html>
<head>
<meta charset="utf-8">
<meta content="IE=edge,chrome=1" http-equiv="X-UA-Compatible">
<meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1">
<% if current_page.data.key?('meta') %>
<% current_page.data.meta.each do |meta| %>
<meta
<% meta.each do |key, value| %>
<%= "#{key}=\"#{value}\"" %>
<% end %>
>
<% end %>
<% end %>
<title><%= current_page.data.title || "API Documentation" %></title>
<style media="screen">
<%= Rouge::Themes::MonokaiSublimeSlate.render(:scope => '.highlight') %>
</style>
<style media="print">
* {
transition:none!important;
}
<%= Rouge::Themes::Base16::Solarized.render(:scope => '.highlight') %>
</style>
<%= stylesheet_link_tag :screen, media: :screen %>
<%= stylesheet_link_tag :print, media: :print %>
<% if current_page.data.search %>
<%= javascript_include_tag "all" %>
<% else %>
<%= javascript_include_tag "all_nosearch" %>
<% end %>
<% if current_page.data.code_clipboard %>
<script>
$(function() { setupCodeCopy(); });
</script>
<% end %>
</head>
<body class="<%= page_classes %>" data-languages="<%=h language_tabs.map{ |lang| lang.is_a?(Hash) ? lang.keys.first : lang }.to_json %>">
<a href="#" id="nav-button">
<span>
NAV
<%= image_tag('navbar.png') %>
</span>
</a>
<div class="toc-wrapper">
<%= image_tag "logo.png", class: 'logo' %>
<% if language_tabs.any? %>
<div class="lang-selector">
<% language_tabs.each do |lang| %>
<% if lang.is_a? Hash %>
<a href="#" data-language-name="<%= lang.keys.first %>"><%= lang.values.first %></a>
<% else %>
<a href="#" data-language-name="<%= lang %>"><%= lang %></a>
<% end %>
<% end %>
</div>
<% end %>
<% if current_page.data.search %>
<div class="search">
<input type="text" class="search" id="input-search" placeholder="Search">
</div>
<ul class="search-results"></ul>
<% end %>
<ul id="toc" class="toc-list-h1">
<% toc_data(page_content).each do |h1| %>
<li>
<a href="#<%= h1[:id] %>" class="toc-h1 toc-link" data-title="<%= h1[:title] %>"><%= h1[:content] %></a>
<% if h1[:children].length > 0 %>
<ul class="toc-list-h2">
<% h1[:children].each do |h2| %>
<li>
<a href="#<%= h2[:id] %>" class="toc-h2 toc-link" data-title="<%= h2[:title] %>"><%= h2[:content] %></a>
</li>
<% end %>
</ul>
<% end %>
</li>
<% end %>
</ul>
<% if current_page.data.toc_footers %>
<ul class="toc-footer">
<% current_page.data.toc_footers.each do |footer| %>
<li><%= footer %></li>
<% end %>
</ul>
<% end %>
</div>
<div class="page-wrapper">
<div class="dark-box"></div>
<div class="content">
<%= page_content %>
</div>
<div class="dark-box">
<% if language_tabs.any? %>
<div class="lang-selector">
<% language_tabs.each do |lang| %>
<% if lang.is_a? Hash %>
<a href="#" data-language-name="<%= lang.keys.first %>"><%= lang.values.first %></a>
<% else %>
<a href="#" data-language-name="<%= lang %>"><%= lang %></a>
<% end %>
<% end %>
</div>
<% end %>
</div>
</div>
</body>
</html>

View File

@ -1,38 +0,0 @@
@font-face {
font-family: 'slate';
src:font-url('slate.eot?-syv14m');
src:font-url('slate.eot?#iefix-syv14m') format('embedded-opentype'),
font-url('slate.woff2?-syv14m') format('woff2'),
font-url('slate.woff?-syv14m') format('woff'),
font-url('slate.ttf?-syv14m') format('truetype'),
font-url('slate.svg?-syv14m#slate') format('svg');
font-weight: normal;
font-style: normal;
}
%icon {
font-family: 'slate';
speak: none;
font-style: normal;
font-weight: normal;
font-variant: normal;
text-transform: none;
line-height: 1;
}
%icon-exclamation-sign {
@extend %icon;
content: "\e600";
}
%icon-info-sign {
@extend %icon;
content: "\e602";
}
%icon-ok-sign {
@extend %icon;
content: "\e606";
}
%icon-search {
@extend %icon;
content: "\e607";
}

View File

@ -1,427 +0,0 @@
/*! normalize.css v3.0.2 | MIT License | git.io/normalize */
/**
* 1. Set default font family to sans-serif.
* 2. Prevent iOS text size adjust after orientation change, without disabling
* user zoom.
*/
html {
font-family: sans-serif; /* 1 */
-ms-text-size-adjust: 100%; /* 2 */
-webkit-text-size-adjust: 100%; /* 2 */
}
/**
* Remove default margin.
*/
body {
margin: 0;
}
/* HTML5 display definitions
========================================================================== */
/**
* Correct `block` display not defined for any HTML5 element in IE 8/9.
* Correct `block` display not defined for `details` or `summary` in IE 10/11
* and Firefox.
* Correct `block` display not defined for `main` in IE 11.
*/
article,
aside,
details,
figcaption,
figure,
footer,
header,
hgroup,
main,
menu,
nav,
section,
summary {
display: block;
}
/**
* 1. Correct `inline-block` display not defined in IE 8/9.
* 2. Normalize vertical alignment of `progress` in Chrome, Firefox, and Opera.
*/
audio,
canvas,
progress,
video {
display: inline-block; /* 1 */
vertical-align: baseline; /* 2 */
}
/**
* Prevent modern browsers from displaying `audio` without controls.
* Remove excess height in iOS 5 devices.
*/
audio:not([controls]) {
display: none;
height: 0;
}
/**
* Address `[hidden]` styling not present in IE 8/9/10.
* Hide the `template` element in IE 8/9/11, Safari, and Firefox < 22.
*/
[hidden],
template {
display: none;
}
/* Links
========================================================================== */
/**
* Remove the gray background color from active links in IE 10.
*/
a {
background-color: transparent;
}
/**
* Improve readability when focused and also mouse hovered in all browsers.
*/
a:active,
a:hover {
outline: 0;
}
/* Text-level semantics
========================================================================== */
/**
* Address styling not present in IE 8/9/10/11, Safari, and Chrome.
*/
abbr[title] {
border-bottom: 1px dotted;
}
/**
* Address style set to `bolder` in Firefox 4+, Safari, and Chrome.
*/
b,
strong {
font-weight: bold;
}
/**
* Address styling not present in Safari and Chrome.
*/
dfn {
font-style: italic;
}
/**
* Address variable `h1` font-size and margin within `section` and `article`
* contexts in Firefox 4+, Safari, and Chrome.
*/
h1 {
font-size: 2em;
margin: 0.67em 0;
}
/**
* Address styling not present in IE 8/9.
*/
mark {
background: #ff0;
color: #000;
}
/**
* Address inconsistent and variable font size in all browsers.
*/
small {
font-size: 80%;
}
/**
* Prevent `sub` and `sup` affecting `line-height` in all browsers.
*/
sub,
sup {
font-size: 75%;
line-height: 0;
position: relative;
vertical-align: baseline;
}
sup {
top: -0.5em;
}
sub {
bottom: -0.25em;
}
/* Embedded content
========================================================================== */
/**
* Remove border when inside `a` element in IE 8/9/10.
*/
img {
border: 0;
}
/**
* Correct overflow not hidden in IE 9/10/11.
*/
svg:not(:root) {
overflow: hidden;
}
/* Grouping content
========================================================================== */
/**
* Address margin not present in IE 8/9 and Safari.
*/
figure {
margin: 1em 40px;
}
/**
* Address differences between Firefox and other browsers.
*/
hr {
-moz-box-sizing: content-box;
box-sizing: content-box;
height: 0;
}
/**
* Contain overflow in all browsers.
*/
pre {
overflow: auto;
}
/**
* Address odd `em`-unit font size rendering in all browsers.
*/
code,
kbd,
pre,
samp {
font-family: monospace, monospace;
font-size: 1em;
}
/* Forms
========================================================================== */
/**
* Known limitation: by default, Chrome and Safari on OS X allow very limited
* styling of `select`, unless a `border` property is set.
*/
/**
* 1. Correct color not being inherited.
* Known issue: affects color of disabled elements.
* 2. Correct font properties not being inherited.
* 3. Address margins set differently in Firefox 4+, Safari, and Chrome.
*/
button,
input,
optgroup,
select,
textarea {
color: inherit; /* 1 */
font: inherit; /* 2 */
margin: 0; /* 3 */
}
/**
* Address `overflow` set to `hidden` in IE 8/9/10/11.
*/
button {
overflow: visible;
}
/**
* Address inconsistent `text-transform` inheritance for `button` and `select`.
* All other form control elements do not inherit `text-transform` values.
* Correct `button` style inheritance in Firefox, IE 8/9/10/11, and Opera.
* Correct `select` style inheritance in Firefox.
*/
button,
select {
text-transform: none;
}
/**
* 1. Avoid the WebKit bug in Android 4.0.* where (2) destroys native `audio`
* and `video` controls.
* 2. Correct inability to style clickable `input` types in iOS.
* 3. Improve usability and consistency of cursor style between image-type
* `input` and others.
*/
button,
html input[type="button"], /* 1 */
input[type="reset"],
input[type="submit"] {
-webkit-appearance: button; /* 2 */
cursor: pointer; /* 3 */
}
/**
* Re-set default cursor for disabled elements.
*/
button[disabled],
html input[disabled] {
cursor: default;
}
/**
* Remove inner padding and border in Firefox 4+.
*/
button::-moz-focus-inner,
input::-moz-focus-inner {
border: 0;
padding: 0;
}
/**
* Address Firefox 4+ setting `line-height` on `input` using `!important` in
* the UA stylesheet.
*/
input {
line-height: normal;
}
/**
* It's recommended that you don't attempt to style these elements.
* Firefox's implementation doesn't respect box-sizing, padding, or width.
*
* 1. Address box sizing set to `content-box` in IE 8/9/10.
* 2. Remove excess padding in IE 8/9/10.
*/
input[type="checkbox"],
input[type="radio"] {
box-sizing: border-box; /* 1 */
padding: 0; /* 2 */
}
/**
* Fix the cursor style for Chrome's increment/decrement buttons. For certain
* `font-size` values of the `input`, it causes the cursor style of the
* decrement button to change from `default` to `text`.
*/
input[type="number"]::-webkit-inner-spin-button,
input[type="number"]::-webkit-outer-spin-button {
height: auto;
}
/**
* 1. Address `appearance` set to `searchfield` in Safari and Chrome.
* 2. Address `box-sizing` set to `border-box` in Safari and Chrome
* (include `-moz` to future-proof).
*/
input[type="search"] {
-webkit-appearance: textfield; /* 1 */
-moz-box-sizing: content-box;
-webkit-box-sizing: content-box; /* 2 */
box-sizing: content-box;
}
/**
* Remove inner padding and search cancel button in Safari and Chrome on OS X.
* Safari (but not Chrome) clips the cancel button when the search input has
* padding (and `textfield` appearance).
*/
input[type="search"]::-webkit-search-cancel-button,
input[type="search"]::-webkit-search-decoration {
-webkit-appearance: none;
}
/**
* Define consistent border, margin, and padding.
*/
fieldset {
border: 1px solid #c0c0c0;
margin: 0 2px;
padding: 0.35em 0.625em 0.75em;
}
/**
* 1. Correct `color` not being inherited in IE 8/9/10/11.
* 2. Remove padding so people aren't caught out if they zero out fieldsets.
*/
legend {
border: 0; /* 1 */
padding: 0; /* 2 */
}
/**
* Remove default vertical scrollbar in IE 8/9/10/11.
*/
textarea {
overflow: auto;
}
/**
* Don't inherit the `font-weight` (applied by a rule above).
* NOTE: the default cannot safely be changed in Chrome and Safari on OS X.
*/
optgroup {
font-weight: bold;
}
/* Tables
========================================================================== */
/**
* Remove most spacing between table cells.
*/
table {
border-collapse: collapse;
border-spacing: 0;
}
td,
th {
padding: 0;
}

View File

@ -1,140 +0,0 @@
////////////////////////////////////////////////////////////////////////////////
// RTL Styles Variables
////////////////////////////////////////////////////////////////////////////////
$default: auto;
////////////////////////////////////////////////////////////////////////////////
// TABLE OF CONTENTS
////////////////////////////////////////////////////////////////////////////////
#toc>ul>li>a>span {
float: left;
}
.toc-wrapper {
transition: right 0.3s ease-in-out !important;
left: $default !important;
#{right}: 0;
}
.toc-h2 {
padding-#{right}: $nav-padding + $nav-indent;
}
#nav-button {
#{right}: 0;
transition: right 0.3s ease-in-out;
&.open {
right: $nav-width
}
}
////////////////////////////////////////////////////////////////////////////////
// PAGE LAYOUT AND CODE SAMPLE BACKGROUND
////////////////////////////////////////////////////////////////////////////////
.page-wrapper {
margin-#{left}: $default !important;
margin-#{right}: $nav-width;
.dark-box {
#{right}: $default;
#{left}: 0;
}
}
.lang-selector {
width: $default !important;
a {
float: right;
}
}
////////////////////////////////////////////////////////////////////////////////
// CODE SAMPLE STYLES
////////////////////////////////////////////////////////////////////////////////
.content {
&>h1,
&>h2,
&>h3,
&>h4,
&>h5,
&>h6,
&>p,
&>table,
&>ul,
&>ol,
&>aside,
&>dl {
margin-#{left}: $examples-width;
margin-#{right}: $default !important;
}
&>ul,
&>ol {
padding-#{right}: $main-padding + 15px;
}
table {
th,
td {
text-align: right;
}
}
dd {
margin-#{right}: 15px;
}
aside {
aside:before {
padding-#{left}: 0.5em;
}
.search-highlight {
background: linear-gradient(to top right, #F7E633 0%, #F1D32F 100%);
}
}
pre,
blockquote {
float: left !important;
clear: left !important;
}
}
////////////////////////////////////////////////////////////////////////////////
// TYPOGRAPHY
////////////////////////////////////////////////////////////////////////////////
h1,
h2,
h3,
h4,
h5,
h6,
p,
aside {
text-align: right;
direction: rtl;
}
.toc-wrapper {
text-align: right;
direction: rtl;
font-weight: 100 !important;
}
////////////////////////////////////////////////////////////////////////////////
// RESPONSIVE DESIGN
////////////////////////////////////////////////////////////////////////////////
@media (max-width: $tablet-width) {
.toc-wrapper {
#{right}: -$nav-width;
&.open {
#{right}: 0;
}
}
.page-wrapper {
margin-#{right}: 0;
}
}
@media (max-width: $phone-width) {
%left-col {
margin-#{left}: 0;
}
}

View File

@ -1,103 +0,0 @@
/*
Copyright 2008-2013 Concur Technologies, Inc.
Licensed under the Apache License, Version 2.0 (the "License"); you may
not use this file except in compliance with the License. You may obtain
a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
License for the specific language governing permissions and limitations
under the License.
*/
////////////////////////////////////////////////////////////////////////////////
// CUSTOMIZE SLATE
////////////////////////////////////////////////////////////////////////////////
// Use these settings to help adjust the appearance of Slate
// BACKGROUND COLORS
////////////////////
$nav-bg: #2E3336 !default;
$examples-bg: #2E3336 !default;
$code-bg: #1E2224 !default;
$code-annotation-bg: #191D1F !default;
$nav-subitem-bg: #1E2224 !default;
$nav-active-bg: #0F75D4 !default;
$nav-active-parent-bg: #1E2224 !default; // parent links of the current section
$lang-select-border: #000 !default;
$lang-select-bg: #1E2224 !default;
$lang-select-active-bg: $examples-bg !default; // feel free to change this to blue or something
$lang-select-pressed-bg: #111 !default; // color of language tab bg when mouse is pressed
$main-bg: #F3F7F9 !default;
$aside-notice-bg: #8fbcd4 !default;
$aside-warning-bg: #c97a7e !default;
$aside-success-bg: #6ac174 !default;
$search-notice-bg: #c97a7e !default;
// TEXT COLORS
////////////////////
$main-text: #333 !default; // main content text color
$nav-text: #fff !default;
$nav-active-text: #fff !default;
$nav-active-parent-text: #fff !default; // parent links of the current section
$lang-select-text: #fff !default; // color of unselected language tab text
$lang-select-active-text: #fff !default; // color of selected language tab text
$lang-select-pressed-text: #fff !default; // color of language tab text when mouse is pressed
// SIZES
////////////////////
$nav-width: 230px !default; // width of the navbar
$examples-width: 50% !default; // portion of the screen taken up by code examples
$logo-margin: 0px !default; // margin below logo
$main-padding: 28px !default; // padding to left and right of content & examples
$nav-padding: 15px !default; // padding to left and right of navbar
$nav-v-padding: 10px !default; // padding used vertically around search boxes and results
$nav-indent: 10px !default; // extra padding for ToC subitems
$code-annotation-padding: 13px !default; // padding inside code annotations
$h1-margin-bottom: 21px !default; // padding under the largest header tags
$tablet-width: 930px !default; // min width before reverting to tablet size
$phone-width: $tablet-width - $nav-width !default; // min width before reverting to mobile size
// FONTS
////////////////////
%default-font {
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol";
font-size: 14px;
}
%header-font {
@extend %default-font;
font-weight: bold;
}
%code-font {
font-family: Consolas, Menlo, Monaco, "Lucida Console", "Liberation Mono", "DejaVu Sans Mono", "Bitstream Vera Sans Mono", "Courier New", monospace, serif;
font-size: 12px;
line-height: 1.5;
}
// OTHER
////////////////////
$nav-footer-border-color: #666 !default;
$search-box-border-color: #666 !default;
////////////////////////////////////////////////////////////////////////////////
// INTERNAL
////////////////////////////////////////////////////////////////////////////////
// These settings are probably best left alone.
%break-words {
word-break: break-all;
hyphens: auto;
}

View File

@ -1,153 +0,0 @@
@charset "utf-8";
@import 'normalize';
@import 'variables';
@import 'icon-font';
/*
Copyright 2008-2013 Concur Technologies, Inc.
Licensed under the Apache License, Version 2.0 (the "License"); you may
not use this file except in compliance with the License. You may obtain
a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
License for the specific language governing permissions and limitations
under the License.
*/
$print-color: #999;
$print-color-light: #ccc;
$print-font-size: 12px;
body {
@extend %default-font;
}
.tocify, .toc-footer, .lang-selector, .search, #nav-button {
display: none;
}
.tocify-wrapper>img {
margin: 0 auto;
display: block;
}
.content {
font-size: 12px;
pre, code {
@extend %code-font;
@extend %break-words;
border: 1px solid $print-color;
border-radius: 5px;
font-size: 0.8em;
}
pre {
code {
border: 0;
}
}
pre {
padding: 1.3em;
}
code {
padding: 0.2em;
}
table {
border: 1px solid $print-color;
tr {
border-bottom: 1px solid $print-color;
}
td,th {
padding: 0.7em;
}
}
p {
line-height: 1.5;
}
a {
text-decoration: none;
color: #000;
}
h1 {
@extend %header-font;
font-size: 2.5em;
padding-top: 0.5em;
padding-bottom: 0.5em;
margin-top: 1em;
margin-bottom: $h1-margin-bottom;
border: 2px solid $print-color-light;
border-width: 2px 0;
text-align: center;
}
h2 {
@extend %header-font;
font-size: 1.8em;
margin-top: 2em;
border-top: 2px solid $print-color-light;
padding-top: 0.8em;
}
h1+h2, h1+div+h2 {
border-top: none;
padding-top: 0;
margin-top: 0;
}
h3, h4 {
@extend %header-font;
font-size: 0.8em;
margin-top: 1.5em;
margin-bottom: 0.8em;
text-transform: uppercase;
}
h5, h6 {
text-transform: uppercase;
}
aside {
padding: 1em;
border: 1px solid $print-color-light;
border-radius: 5px;
margin-top: 1.5em;
margin-bottom: 1.5em;
line-height: 1.6;
}
aside:before {
vertical-align: middle;
padding-right: 0.5em;
font-size: 14px;
}
aside.notice:before {
@extend %icon-info-sign;
}
aside.warning:before {
@extend %icon-exclamation-sign;
}
aside.success:before {
@extend %icon-ok-sign;
}
}
.copy-clipboard {
@media print {
display: none
}
}

View File

@ -1,633 +0,0 @@
@charset "utf-8";
@import 'normalize';
@import 'variables';
@import 'icon-font';
// @import 'rtl'; // uncomment to switch to RTL format
/*
Copyright 2008-2013 Concur Technologies, Inc.
Licensed under the Apache License, Version 2.0 (the "License"); you may
not use this file except in compliance with the License. You may obtain
a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
License for the specific language governing permissions and limitations
under the License.
*/
////////////////////////////////////////////////////////////////////////////////
// GENERAL STUFF
////////////////////////////////////////////////////////////////////////////////
html, body {
color: $main-text;
padding: 0;
margin: 0;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
@extend %default-font;
background-color: $main-bg;
height: 100%;
-webkit-text-size-adjust: none; /* Never autoresize text */
}
////////////////////////////////////////////////////////////////////////////////
// TABLE OF CONTENTS
////////////////////////////////////////////////////////////////////////////////
#toc > ul > li > a > span {
float: right;
background-color: #2484FF;
border-radius: 40px;
width: 20px;
}
.toc-wrapper {
transition: left 0.3s ease-in-out;
overflow-y: auto;
overflow-x: hidden;
position: fixed;
z-index: 30;
top: 0;
left: 0;
bottom: 0;
width: $nav-width;
background-color: $nav-bg;
font-size: 13px;
font-weight: bold;
// language selector for mobile devices
.lang-selector {
display: none;
a {
padding-top: 0.5em;
padding-bottom: 0.5em;
}
}
// This is the logo at the top of the ToC
.logo {
display: block;
max-width: 100%;
margin-bottom: $logo-margin;
}
&>.search {
position: relative;
input {
background: $nav-bg;
border-width: 0 0 1px 0;
border-color: $search-box-border-color;
padding: 6px 0 6px 20px;
box-sizing: border-box;
margin: $nav-v-padding $nav-padding;
width: $nav-width - ($nav-padding*2);
outline: none;
color: $nav-text;
border-radius: 0; /* ios has a default border radius */
}
&:before {
position: absolute;
top: 17px;
left: $nav-padding;
color: $nav-text;
@extend %icon-search;
}
}
.search-results {
margin-top: 0;
box-sizing: border-box;
height: 0;
overflow-y: auto;
overflow-x: hidden;
transition-property: height, margin;
transition-duration: 180ms;
transition-timing-function: ease-in-out;
background: $nav-subitem-bg;
&.visible {
height: 30%;
margin-bottom: 1em;
}
li {
margin: 1em $nav-padding;
line-height: 1;
}
a {
color: $nav-text;
text-decoration: none;
&:hover {
text-decoration: underline;
}
}
}
// The Table of Contents is composed of multiple nested
// unordered lists. These styles remove the default
// styling of an unordered list because it is ugly.
ul, li {
list-style: none;
margin: 0;
padding: 0;
line-height: 28px;
}
li {
color: $nav-text;
transition-property: background;
transition-timing-function: linear;
transition-duration: 200ms;
}
// This is the currently selected ToC entry
.toc-link.active {
background-color: $nav-active-bg;
color: $nav-active-text;
}
// this is parent links of the currently selected ToC entry
.toc-link.active-parent {
background-color: $nav-active-parent-bg;
color: $nav-active-parent-text;
}
.toc-list-h2 {
display: none;
background-color: $nav-subitem-bg;
font-weight: 500;
}
.toc-h2 {
padding-left: $nav-padding + $nav-indent;
font-size: 12px;
}
.toc-footer {
padding: 1em 0;
margin-top: 1em;
border-top: 1px dashed $nav-footer-border-color;
li,a {
color: $nav-text;
text-decoration: none;
}
a:hover {
text-decoration: underline;
}
li {
font-size: 0.8em;
line-height: 1.7;
text-decoration: none;
}
}
}
.toc-link, .toc-footer li {
padding: 0 $nav-padding 0 $nav-padding;
display: block;
overflow-x: hidden;
white-space: nowrap;
text-overflow: ellipsis;
text-decoration: none;
color: $nav-text;
transition-property: background;
transition-timing-function: linear;
transition-duration: 130ms;
}
// button to show navigation on mobile devices
#nav-button {
span {
display: block;
$side-pad: $main-padding / 2 - 8px;
padding: $side-pad $side-pad $side-pad;
background-color: rgba($main-bg, 0.7);
transform-origin: 0 0;
transform: rotate(-90deg) translate(-100%, 0);
border-radius: 0 0 0 5px;
}
padding: 0 1.5em 5em 0; // increase touch size area
display: none;
position: fixed;
top: 0;
left: 0;
z-index: 100;
color: #000;
text-decoration: none;
font-weight: bold;
opacity: 0.7;
line-height: 16px;
img {
height: 16px;
vertical-align: bottom;
}
transition: left 0.3s ease-in-out;
&:hover { opacity: 1; }
&.open {left: $nav-width}
}
////////////////////////////////////////////////////////////////////////////////
// PAGE LAYOUT AND CODE SAMPLE BACKGROUND
////////////////////////////////////////////////////////////////////////////////
.page-wrapper {
margin-left: $nav-width;
position: relative;
z-index: 10;
background-color: $main-bg;
min-height: 100%;
padding-bottom: 1px; // prevent margin overflow
// The dark box is what gives the code samples their dark background.
// It sits essentially under the actual content block, which has a
// transparent background.
// I know, it's hackish, but it's the simplist way to make the left
// half of the content always this background color.
.dark-box {
width: $examples-width;
background-color: $examples-bg;
position: absolute;
right: 0;
top: 0;
bottom: 0;
}
.lang-selector {
position: fixed;
z-index: 50;
border-bottom: 5px solid $lang-select-active-bg;
}
}
.lang-selector {
display: flex;
background-color: $lang-select-bg;
width: 100%;
font-weight: bold;
overflow-x: auto;
a {
display: inline;
color: $lang-select-text;
text-decoration: none;
padding: 0 10px;
line-height: 30px;
outline: 0;
&:active, &:focus {
background-color: $lang-select-pressed-bg;
color: $lang-select-pressed-text;
}
&.active {
background-color: $lang-select-active-bg;
color: $lang-select-active-text;
}
}
&:after {
content: '';
clear: both;
display: block;
}
}
////////////////////////////////////////////////////////////////////////////////
// CONTENT STYLES
////////////////////////////////////////////////////////////////////////////////
// This is all the stuff with the light background in the left half of the page
.content {
// fixes webkit rendering bug for some: see #538
-webkit-transform: translateZ(0);
// to place content above the dark box
position: relative;
z-index: 30;
&:after {
content: '';
display: block;
clear: both;
}
&>h1, &>h2, &>h3, &>h4, &>h5, &>h6, &>p, &>table, &>ul, &>ol, &>aside, &>dl {
margin-right: $examples-width;
padding: 0 $main-padding;
box-sizing: border-box;
display: block;
@extend %left-col;
}
&>ul, &>ol {
padding-left: $main-padding + 15px;
}
// the div is the tocify hidden div for placeholding stuff
&>h1, &>h2, &>div {
clear:both;
}
h1 {
@extend %header-font;
font-size: 25px;
padding-top: 0.5em;
padding-bottom: 0.5em;
margin-bottom: $h1-margin-bottom;
margin-top: 2em;
border-top: 1px solid #ccc;
border-bottom: 1px solid #ccc;
background-color: #fdfdfd;
}
h1:first-child, div:first-child + h1 {
border-top-width: 0;
margin-top: 0;
}
h2 {
@extend %header-font;
font-size: 19px;
margin-top: 4em;
margin-bottom: 0;
border-top: 1px solid #ccc;
padding-top: 1.2em;
padding-bottom: 1.2em;
background-image: linear-gradient(to bottom, rgba(#fff, 0.2), rgba(#fff, 0));
}
// h2s right after h1s should bump right up
// against the h1s.
h1 + h2, h1 + div + h2 {
margin-top: $h1-margin-bottom * -1;
border-top: none;
}
h3, h4, h5, h6 {
@extend %header-font;
font-size: 15px;
margin-top: 2.5em;
margin-bottom: 0.8em;
}
h4, h5, h6 {
font-size: 10px;
}
hr {
margin: 2em 0;
border-top: 2px solid $examples-bg;
border-bottom: 2px solid $main-bg;
}
table {
margin-bottom: 1em;
overflow: auto;
th,td {
text-align: left;
vertical-align: top;
line-height: 1.6;
code {
white-space: nowrap;
}
}
th {
padding: 5px 10px;
border-bottom: 1px solid #ccc;
vertical-align: bottom;
}
td {
padding: 10px;
}
tr:last-child {
border-bottom: 1px solid #ccc;
}
tr:nth-child(odd)>td {
background-color: lighten($main-bg,4.2%);
}
tr:nth-child(even)>td {
background-color: lighten($main-bg,2.4%);
}
}
dt {
font-weight: bold;
}
dd {
margin-left: 15px;
}
p, li, dt, dd {
line-height: 1.6;
margin-top: 0;
}
img {
max-width: 100%;
}
code {
background-color: rgba(0,0,0,0.05);
padding: 3px;
border-radius: 3px;
@extend %break-words;
@extend %code-font;
}
pre>code {
background-color: transparent;
padding: 0;
}
aside {
padding-top: 1em;
padding-bottom: 1em;
margin-top: 1.5em;
margin-bottom: 1.5em;
background: $aside-notice-bg;
line-height: 1.6;
&.warning {
background-color: $aside-warning-bg;
}
&.success {
background-color: $aside-success-bg;
}
}
aside:before {
vertical-align: middle;
padding-right: 0.5em;
font-size: 14px;
}
aside.notice:before {
@extend %icon-info-sign;
}
aside.warning:before {
@extend %icon-exclamation-sign;
}
aside.success:before {
@extend %icon-ok-sign;
}
.search-highlight {
padding: 2px;
margin: -3px;
border-radius: 4px;
border: 1px solid #F7E633;
background: linear-gradient(to top left, #F7E633 0%, #F1D32F 100%);
}
}
////////////////////////////////////////////////////////////////////////////////
// CODE SAMPLE STYLES
////////////////////////////////////////////////////////////////////////////////
// This is all the stuff that appears in the right half of the page
.content {
&>div.highlight {
clear:none;
}
pre, blockquote {
background-color: $code-bg;
color: #fff;
margin: 0;
width: $examples-width;
float:right;
clear:right;
box-sizing: border-box;
@extend %right-col;
&>p { margin: 0; }
a {
color: #fff;
text-decoration: none;
border-bottom: dashed 1px #ccc;
}
}
pre {
@extend %code-font;
padding-top: 2em;
padding-bottom: 2em;
padding: 2em $main-padding;
}
blockquote {
&>p {
background-color: $code-annotation-bg;
padding: $code-annotation-padding 2em;
color: #eee;
}
}
}
////////////////////////////////////////////////////////////////////////////////
// RESPONSIVE DESIGN
////////////////////////////////////////////////////////////////////////////////
// These are the styles for phones and tablets
// There are also a couple styles disperesed
@media (max-width: $tablet-width) {
.toc-wrapper {
left: -$nav-width;
&.open {
left: 0;
}
}
.page-wrapper {
margin-left: 0;
}
#nav-button {
display: block;
}
.toc-link {
padding-top: 0.3em;
padding-bottom: 0.3em;
}
}
@media (max-width: $phone-width) {
.dark-box {
display: none;
}
%left-col {
margin-right: 0;
}
.toc-wrapper .lang-selector {
display: block;
}
.page-wrapper .lang-selector {
display: none;
}
%right-col {
width: auto;
float: none;
}
%right-col + %left-col {
margin-top: $main-padding;
}
}
.highlight .c, .highlight .cm, .highlight .c1, .highlight .cs {
color: #909090;
}
.highlight, .highlight .w {
background-color: $code-bg;
}
.copy-clipboard {
float: right;
fill: #9DAAB6;
cursor: pointer;
opacity: 0.4;
height: 18px;
width: 18px;
}
.copy-clipboard:hover {
opacity: 0.8;
}

View File

@ -1,6 +0,0 @@
---
title: "{{ replace .Name "-" " " | title }}"
date: {{ .Date }}
draft: true
---

View File

@ -1,115 +0,0 @@
# hugo server --minify --themesDir ... --baseURL=http://0.0.0.0:1313/theme/hugo-book/
baseURL = 'https://rustybever.be/docs/vieter/'
title = 'Vieter - Docs'
theme = 'hugo-book'
# Book configuration
disablePathToLower = true
# Doesn't work with docs as subdir
enableGitInfo = true
# Needed for mermaid/katex shortcodes
[markup]
[markup.goldmark.renderer]
unsafe = true
[markup.tableOfContents]
startLevel = 1
# Multi-lingual mode config
# There are different options to translate files
# See https://gohugo.io/content-management/multilingual/#translation-by-filename
# And https://gohugo.io/content-management/multilingual/#translation-by-content-directory
[languages]
[languages.en]
languageName = 'English'
contentDir = 'content'
weight = 1
[menu]
[[menu.after]]
name = "HTTP API Docs"
url = "https://rustybever.be/docs/vieter/api/"
weight = 10
[[menu.after]]
name = "Man Pages"
url = "https://rustybever.be/man/vieter/vieter.1.html"
weight = 20
[[menu.after]]
name = "Vieter"
url = "https://git.rustybever.be/vieter-v/vieter"
weight = 30
[[menu.after]]
name = "Hugo Theme"
url = "https://github.com/alex-shpak/hugo-book"
weight = 40
[params]
# (Optional, default light) Sets color theme: light, dark or auto.
# Theme 'auto' switches between dark and light modes based on browser/os preferences
BookTheme = 'auto'
# (Optional, default true) Controls table of contents visibility on right side of pages.
# Start and end levels can be controlled with markup.tableOfContents setting.
# You can also specify this parameter per page in front matter.
BookToC = true
# (Optional, default none) Set the path to a logo for the book. If the logo is
# /static/logo.png then the path would be logo.png
# BookLogo = 'logo.png'
# (Optional, default none) Set leaf bundle to render as side menu
# When not specified file structure and weights will be used
# BookMenuBundle = '/menu'
# (Optional, default docs) Specify root page to render child pages as menu.
# Page is resoled by .GetPage function: https://gohugo.io/functions/getpage/
# For backward compatibility you can set '*' to render all sections to menu. Acts same as '/'
BookSection = '/'
# Set source repository location.
# Used for 'Last Modified' and 'Edit this page' links.
BookRepo = 'https://git.rustybever.be/vieter-v/vieter'
# (Optional, default 'commit') Specifies commit portion of the link to the page's last modified
# commit hash for 'doc' page type.
# Requires 'BookRepo' param.
# Value used to construct a URL consisting of BookRepo/BookCommitPath/<commit-hash>
# Github uses 'commit', Bitbucket uses 'commits'
BookCommitPath = 'src/commit'
# Enable "Edit this page" links for 'doc' page type.
# Disabled by default. Uncomment to enable. Requires 'BookRepo' param.
# Edit path must point to root directory of repo.
# BookEditPath = 'edit/main/exampleSite'
# Configure the date format used on the pages
# - In git information
# - In blog posts
BookDateFormat = 'January 2, 2006'
# (Optional, default true) Enables search function with flexsearch,
# Index is built on fly, therefore it might slowdown your website.
# Configuration for indexing can be adjusted in i18n folder per language.
BookSearch = true
# (Optional, default true) Enables comments template on pages
# By default partals/docs/comments.html includes Disqus template
# See https://gohugo.io/content-management/comments/#configure-disqus
# Can be overwritten by same param in page frontmatter
BookComments = false
# /!\ This is an experimental feature, might be removed or changed at any time
# (Optional, experimental, default false) Enables portable links and link checks in markdown pages.
# Portable links meant to work with text editors and let you write markdown without {{< relref >}} shortcode
# Theme will print warning if page referenced in markdown does not exists.
BookPortableLinks = true
# /!\ This is an experimental feature, might be removed or changed at any time
# (Optional, experimental, default false) Enables service worker that caches visited pages and resources for offline use.
BookServiceWorker = true
# /!\ This is an experimental feature, might be removed or changed at any time
# (Optional, experimental, default false) Enables a drop-down menu for translations only if a translation is present.
BookTranslatedOnly = false

View File

@ -1,59 +0,0 @@
# Vieter
{{< hint warning >}}
**Important**
Because this project is still in heavy development, this documentation tries to
follow the development branch & not the latest release. This means that the
documentation might not be relevant anymore for the latest release.
{{< /hint >}}
## Overview
Vieter consists of two main parts, namely an implementation of an Arch
repository server & a scheduling system to periodically build Pacman packages &
publish them to a repository.
{{< hint info >}}
**Note**
While I mention Vieter being an "Arch" repository server, it works with any
distribution that uses Pacman as the package manager. I do recommend using a
base docker image for your distribution if you wish to use the build system as
well.
{{< /hint >}}
### Why?
Vieter is my personal solution to a problem I've been facing for months:
extremely long AUR package build times. I run EndeavourOS on both my laptops,
one of which being a rather old MacBook Air. I really like being a beta-tester
for projects & run development builds for multiple packages (nheko,
newsflash...). Because of this, I have to regularly re-build these packages in
order to stay up to date with development. However, these builds can take a
really long time on the old MacBook. This project is a solution to that
problem: instead of building the packages locally, I can build them
automatically in the cloud & just download them whenever I update my system!
Thanks to this solution, I'm able to shave 10-15 minutes off my update times,
just from not having to compile everything every time there's an update.
Besides this, it's also just really useful to have a repository server that you
control & can upload your own packages to. For example, I package my st
terminal using a CI pipeline & upload it to my repository!
### Why V?
I had been interested in learning V for a couple of months ever since I
stumbled upon it by accident. It looked like a promising language & turned out
to be very fun to use! It's fast & easy to learn, & it's a nice contrast with
my usual Rust-based projects, which tend to get quite complex.
I recommend checking out their [homepage](https://vlang.io/)!
### What's with the name?
Before deciding to write this project in V, I wrote a prototype in Python,
called [Pieter](https://git.rustybever.be/Chewing_Bever/pieter). The name
Pieter came from Pieter Post, the Dutch name for [Postname
Pat](https://en.wikipedia.org/wiki/Postman_Pat). The idea was that the server
"delivered packages", & a good friend of mine suggested the name. When I
decided to switch over to Vieter, I changed the P (for Python) to a V, it
seemed fitting.

View File

@ -1,138 +0,0 @@
---
weight: 20
---
# Configuration
By default, all vieter commands try to read in the TOML file `~/.vieterrc` for
configuration. The location of this file can be changed by using the `-f` flag.
If the above file doesn't exist or you wish to override some of its settings,
configuration is also possible using environment variables. Every variable in
the config file has a respective environment variable of the following form:
say the variable is called `api_key`, then the respective environment variable
would be `VIETER_API_KEY`. In essence, it's the variable in uppercase prepended
with `VIETER_`.
If a variable is both present in the config file & as an environment variable,
the value in the environment variable is used.
{{< hint info >}}
**Note**
All environment variables can also be provided from a file by appending them
with `_FILE`. This for example allows you to provide the API key from a Docker
secrets file.
{{< /hint >}}
## Commands
The first argument passed to Vieter determines which command you wish to use.
Each of these can contain subcommands (e.g. `vieter targets list`), but all
subcommands will use the same configuration. Below you can find the
configuration variable required for each command.
### `vieter server`
* `port`: HTTP port to run on
* Default: `8000`
* `log_level`: log verbosity level. Value should be one of `FATAL`, `ERROR`,
`WARN`, `INFO` or `DEBUG`.
* Default: `WARN`
* `pkg_dir`: where Vieter should store the actual package archives.
* `data_dir`: where Vieter stores the repositories, log file & database.
* `api_key`: the API key to use when authenticating requests.
* `default_arch`: this setting serves two main purposes:
* Packages with architecture `any` are always added to this architecture.
This prevents the server from being confused when an `any` package is
published as the very first package for a repository.
* Targets added without an `arch` value use this value instead.
* `global_schedule`: build schedule for any target that does not have a
schedule defined. For information about this syntax, see
[here](/usage/builds/schedule).
* Default: `0 3` (3AM every night)
* `base_image`: Docker image to use when building a package. Any Pacman-based
distro image should work, as long as `/etc/pacman.conf` is used &
`base-devel` exists in the repositories. Make sure that the image supports
the architecture of your cron daemon.
* Default: `archlinux:base-devel` (only works on `x86_64`). If you require
`aarch64` support, consider using
[`menci/archlinuxarm:base-devel`](https://hub.docker.com/r/menci/archlinuxarm)
([GitHub](https://github.com/Menci/docker-archlinuxarm)). This is the
image used for the Vieter CI builds.
* `max_log_age`: maximum age of logs (in days). Logs older than this will get
cleaned by the log removal daemon. If set to zero, no logs are ever removed.
The age of logs is determined by the time the build was started.
* Default: `0`
* `log_removal_schedule`: cron schedule defining when to clean old logs.
* Default: `0 0` (every day at midnight)
### `vieter cron`
* `log_level`: log verbosity level. Value should be one of `FATAL`, `ERROR`,
`WARN`, `INFO` or `DEBUG`.
* Default: `WARN`
* `log_file`: log file to write logs to.
* Default: `vieter.log` (in `data_dir`)
* `address`: *public* URL of the Vieter repository server to build for. From
this server the list of Git repositories is retrieved. All built packages are
published to this server.
* `api_key`: API key of the above server.
* `data_dir`: directory to store log file in.
* `base_image`: Docker image to use when building a package. Any Pacman-based
distro image should work, as long as `/etc/pacman.conf` is used &
`base-devel` exists in the repositories. Make sure that the image supports
the architecture of your cron daemon.
* Default: `archlinux:base-devel` (only works on `x86_64`). If you require
`aarch64` support, consider using
[`menci/archlinuxarm:base-devel`](https://hub.docker.com/r/menci/archlinuxarm)
([GitHub](https://github.com/Menci/docker-archlinuxarm)). This is the image
used for the Vieter CI builds.
* `max_concurrent_builds`: how many builds to run at the same time.
* Default: `1`
* `api_update_frequency`: how frequently (in minutes) to poll the Vieter
repository server for a new list of Git repositories to build.
* Default: `15`
* `image_rebuild_frequency`: Vieter periodically builds a builder image using
the configured base image. This makes sure build containers do not have to
download a lot of packages when updating their system. This setting defines
how frequently (in minutes) to rebuild this builder image.
* Default: `1440` (every 24 hours)
* `global_schedule`: build schedule for any Git repository that does not have a
schedule defined. For information about this syntax, see
[here](/usage/builds/schedule).
* Default: `0 3` (3AM every night)
### `vieter logs`
* `api_key`: the API key to use when authenticating requests.
* `address`: Base URL of your Vieter instance, e.g. https://example.com
### `vieter targets`
* `api_key`: the API key to use when authenticating requests.
* `address`: Base URL of your Vieter instance, e.g. https://example.com
* `base_image`: image to use when building a package using `vieter targets
build`.
* Default: `archlinux:base-devel`
### `vieter agent`
* `log_level`: log verbosity level. Value should be one of `FATAL`, `ERROR`,
`WARN`, `INFO` or `DEBUG`.
* Default: `WARN`
* `address`: *public* URL of the Vieter repository server to build for. From
this server jobs are retrieved. All built packages are published to this
server.
* `api_key`: API key of the above server.
* `data_dir`: directory to store log file in.
* `max_concurrent_builds`: how many builds to run at the same time.
* Default: `1`
* `polling_frequency`: how often (in seconds) to poll the server for new
builds. Note that the agent might poll more frequently when it's actively
processing builds.
* `image_rebuild_frequency`: Vieter periodically builds images that are then
used as a basis for running build containers. This is to prevent each build
from downloading an entire repository worth of dependencies. This setting
defines how frequently (in minutes) to rebuild these images.
* Default: `1440` (every 24 hours)
* `arch`: architecture for which this agent should pull down builds (e.g.
`x86_64`)

View File

@ -1,114 +0,0 @@
---
weight: 10
---
# Installation
Vieter consists of a single binary, akin to busybox. The binary's behavior is
determined by its CLI arguments, e.g. `vieter server` starts the repository
server.
All installation solutions can be configured the same way,
as described [here](/configuration).
## Docker
Docker images are published to the
[`chewingbever/vieter`](https://hub.docker.com/r/chewingbever/vieter) Docker
Hub repository. You can either pull a release tag (e.g.
`chewingbever/vieter:0.1.0-rc1`), or pull the `chewingbever/vieter:dev` tag.
The latter is updated every time a new commit is pushed to the development
branch. This branch will be the most up to date, but does not give any
guarantees about stability, so beware!
Thanks to the single-binary design of Vieter, this image can be used both for
the repository server, the cron daemon and the agent.
Below is a minimal compose file to set up both the repository server & a build
agent:
```yaml
version: '3'
services:
server:
image: 'chewingbever/vieter:0.5.0-rc.1'
restart: 'always'
environment:
- 'VIETER_API_KEY=secret'
- 'VIETER_DEFAULT_ARCH=x86_64'
volumes:
- 'data:/data'
cron:
image: 'chewingbever/vieter:0.5.0-rc.1'
restart: 'always'
# Required to connect to the Docker daemon
user: root
command: 'vieter agent'
environment:
- 'VIETER_API_KEY=secret'
# MUST be public URL of Vieter repository
- 'VIETER_ADDRESS=https://example.com'
# Architecture for which the agent builds
- 'VIETER_ARCH=x86_64'
- 'VIETER_MAX_CONCURRENT_BUILDS=2'
volumes:
- '/var/run/docker.sock:/var/run/docker.sock'
volumes:
data:
```
If you do not require the build system, the repository server can be used
independently as well.
Of course, Vieter allows a lot more configuration than this. This compose file
is meant as a starting point for setting up your installation.
{{< hint info >}}
**Note**
Builds are executed on the agent's system using the host's Docker daemon. An
agent for a specific `arch` will only build packages for that specific
architecture. Therefore, if you wish to build packages for both `x86_64` &
`aarch64`, you'll have to deploy two agents, one on each architecture.
Afterwards, any Git repositories enabled for those two architectures will build
on both.
{{< /hint >}}
## Binary
On the
[releases](https://git.rustybever.be/vieter-v/vieter/releases)
page, you can find statically compiled binaries for all
released versions. This is the same binary as used inside
the Docker images.
## Arch
I publish both development & release versions of Vieter to my personal
repository, https://arch.r8r.be. Packages are available for `x86_64` &
`aarch64`. To use the repository, add the following to your `pacman.conf`:
```
[vieter]
Server = https://arch.r8r.be/$repo/$arch
SigLevel = Optional
```
Afterwards, you can update your system & install the `vieter` package for the
latest official release or `vieter-git` for the latest development release.
### AUR
If you prefer building the packages locally (or on your own Vieter instance),
there's the [`vieter`](https://aur.archlinux.org/packages/vieter) &
[`vieter-git`](https://aur.archlinux.org/packages/vieter-git) packages on the
AUR. These packages build using the `vlang` compiler package, so I can't
guarantee that a compiler update won't temporarily break them.
## Building from source
The project [README](https://git.rustybever.be/vieter-v/vieter#building)
contains instructions for building Vieter from source.

View File

@ -1,3 +0,0 @@
---
weight: 30
---

View File

@ -1,51 +0,0 @@
---
weight: 20
---
# Building packages
The automatic build system is what makes Vieter very useful as a replacement
for an AUR helper. It can perodically build packages & publish them to your
personal Vieter repository server, removing the need to build the packages
locally.
## Adding builds
Before the cron system can start building your package, you need to add its
info to the system. The Vieter repository server exposes an HTTP API for this
(see the [HTTP API Docs](https://rustybever.be/docs/vieter/api/) for more
info). For ease of use, the Vieter binary contains a CLI interface for
interacting with this API (see [Configuration](/configuration) for
configuration details). The [man
pages](https://rustybever.be/man/vieter/vieter-targets.1.html) describe this in
greater detail, but the basic usage is as follows:
```
vieter targets add some-url some-repository
```
Here, `some-url` is the URL of the Git repository containing the PKGBUILD. This
URL is passed to `git clone`, meaning the repository should be public. Vieter
expects the same format as an AUR Git repository, so you can directly use AUR
URLs here. Alternatively, you can also provide the URL to a PKGBUILD file
instead. See
[vieter-targets-add(1)](https://rustybever.be/man/vieter/vieter-targets-add.1.html)
for more information.
`some-repo` is the repository to which the built package archives should be
published.
The above command intentionally leaves out a few parameters to make the CLI
more useable. For information on how to modify all parameters using the CLI,
see
[vieter-targets(1)](https://rustybever.be/man/vieter/vieter-targets.1.html).
## Reading logs
The logs of each build are uploaded to the Vieter repository server, along with
information about the exit code of the build container, when the build
started/ended etc. These logs can then be accessed using the [HTTP
API](https://rustybever.be/docs/vieter/api/).
For ease of use, the logs are also available using some CLI commands; see
[vieter-logs(1)](https://rustybever.be/man/vieter/vieter-logs.1.html) for more
information.

View File

@ -1,23 +0,0 @@
---
weight: 20
---
# Cleanup
Vieter stores the logs of every single package build. While this is great for
debugging why builds fail, it also causes an active or long-running Vieter
instance to accumulate thousands of logs.
To combat this, a log removal daemon can be enabled that periodically removes
old build logs. By starting your server with the `max_log_age` variable (see
[Configuration](/configuration#vieter-server)), a daemon will get enabled that
periodically removes logs older than this setting. By default, this will happen
every day at midnight, but this behavior can be changed using the
`log_removal_schedule` variable.
{{< hint info >}}
**Note**
The daemon will always run a removal of logs on startup. Therefore, it's
possible the daemon will be *very* active when first enabling this setting.
After the initial surge of logs to remove, it'll calm down again.
{{< /hint >}}

View File

@ -1,46 +0,0 @@
---
weight: 10
---
# Cron schedule syntax
The Vieter cron daemon uses a subset of the cron expression syntax to schedule
builds.
## Format
`a b c d`
* `a`: minutes
* `b`: hours
* `c`: days
* `d`: months
An expression consists of two to four sections. If less than four sections are
provided, the parser will append `*` until there are four sections. This means
that `0 3` is the same as `0 3 * *`.
Each section consists of one or more parts, separated by a comma. Each of these
parts, in turn, can be one of the following (any letters are integers):
* `*`: allow all possible values.
* `a`: only this value is allowed.
* `*/n`: allow every n-th value.
* `a/n`: allow every n-th value, starting at a in the list.
* `a-b`: allow every value between a and b, bounds included.
* `a-b/n`: allow every n-th value inside the list of values between a and b,
bounds included.
Each section can consist of as many of these parts as necessary.
## Examples
* `0 3`: every day at 03:00AM.
* `0 0 */7`: every 7th day of the month, at midnight.
## CLI tool
The Vieter binary contains a command that shows you the next matching times for
a given expression. This can be useful for understanding the syntax. For more
information, see
[vieter-schedule(1)](https://rustybever.be/man/vieter/vieter-schedule.1.html).

View File

@ -1,41 +0,0 @@
---
weight: 10
---
# Pacman repository
The part of Vieter that users will interact with the most is the Pacman
repository aka `vieter server`.
## Design overview
A Vieter repository server has support for multiple repositories, with each
repository containing packages for multiple architectures.
If you wish to use these repositories on your system, add the following to
`/etc/pacman.conf` for each repository you wish to use:
```
[repo-name]
Server = https://example.com/$repo/$arch
SigLevel = Optional
```
Here, `$repo` and `$arch` are not variables you have to fill in yourself.
Rather, Pacman will substitute these when reading the config file. `$repo` is
replaced by the name between the square brackets (in this case `repo-name`),
and `$arch` is replaced by your system's architecture, e.g. `x86_64`. Of
course, you can also fill in these values manually yourself, e.g. if you wish
to use a different name inside the square brackets.
Important to note is that, when two repositories contain a package with the
same name, Pacman will choose the one from the repository that's highest up in
the `pacman.conf` file. Therefore, if you know your repository has packages
with the same name as ones from the official repositories, it might be better
to place the repository below the official repositories to avoid overwriting
official packages.
## Publishing packages
Packages can be easily published using a single HTTP POST request. Check out
the [HTTP API docs](https://rustybever.be/docs/vieter/api/) for more info on
these routes, including example cURL commands.

File diff suppressed because one or more lines are too long

View File

@ -1 +0,0 @@
{"Target":"book.min.97cfda4f5e3c9fa49a2bf8d401f4ddc0eec576c99cdcf6afbec19173200c37db.css","MediaType":"text/css","Data":{"Integrity":"sha256-l8/aT148n6SaK/jUAfTdwO7Fdsmc3PavvsGRcyAMN9s="}}

View File

@ -1 +0,0 @@
{"Target":"book.min.97cfda4f5e3c9fa49a2bf8d401f4ddc0eec576c99cdcf6afbec19173200c37db.css","MediaType":"text/css","Data":{"Integrity":"sha256-l8/aT148n6SaK/jUAfTdwO7Fdsmc3PavvsGRcyAMN9s="}}

@ -1 +0,0 @@
Subproject commit 4ef38f3bbf5dae9a11a711d2ed1ced9294c6af5f

View File

@ -1,3 +0,0 @@
{
"$schema": "https://docs.renovatebot.com/renovate-schema.json"
}

View File

@ -1,27 +0,0 @@
module agent
import log
import os
import util
const log_file_name = 'vieter.agent.log'
// agent starts an agent service
pub fn agent(conf Config) ! {
log_level := log.level_from_tag(conf.log_level) or {
return error('Invalid log level. The allowed values are FATAL, ERROR, WARN, INFO & DEBUG.')
}
mut logger := log.Log{
level: log_level
}
os.mkdir_all(conf.data_dir) or { util.exit_with_message(1, 'Failed to create data directory.') }
log_file := os.join_path_single(conf.data_dir, agent.log_file_name)
logger.set_full_logpath(log_file)
logger.log_to_console_too()
mut d := agent_init(logger, conf)
d.run()
}

View File

@ -1,31 +0,0 @@
module agent
import cli
import conf as vconf
struct Config {
pub:
log_level string = 'WARN'
// Architecture that the agent represents
arch string
api_key string
address string
data_dir string
max_concurrent_builds int = 1
polling_frequency int = 30
image_rebuild_frequency int = 1440
}
// cmd returns the cli module that handles the cron daemon.
pub fn cmd() cli.Command {
return cli.Command{
name: 'agent'
description: 'Start an agent daemon.'
execute: fn (cmd cli.Command) ! {
config_file := cmd.flags.get_string('config-file')!
conf_ := vconf.load[Config](prefix: 'VIETER_', default_path: config_file)!
agent(conf_)!
}
}
}

View File

@ -1,197 +0,0 @@
module agent
import log
import sync.stdatomic
import build
import models { BuildConfig }
import client
import time
import os
const (
build_empty = 0
build_running = 1
build_done = 2
)
struct AgentDaemon {
logger shared log.Log
conf Config
client client.Client
mut:
images ImageManager
// Atomic variables used to detect when a build has finished; length is
// conf.max_concurrent_builds. This approach is used as the difference
// between a recently finished build and an empty build slot is important
// for knowing whether the agent is currently "active".
atomics []u64
// Channel used to send builds to worker threads
build_channel chan BuildConfig
}
// agent_init initializes a new agent
fn agent_init(logger log.Log, conf Config) AgentDaemon {
mut d := AgentDaemon{
logger: logger
client: client.new(conf.address, conf.api_key)
conf: conf
images: new_image_manager(conf.image_rebuild_frequency * 60)
atomics: []u64{len: conf.max_concurrent_builds}
build_channel: chan BuildConfig{cap: conf.max_concurrent_builds}
}
return d
}
// run starts the actual agent daemon. This function will run forever.
pub fn (mut d AgentDaemon) run() {
// Spawn worker threads
for builder_index in 0 .. d.conf.max_concurrent_builds {
spawn d.builder_thread(d.build_channel, builder_index)
}
// This is just so that the very first time the loop is ran, the jobs are
// always polled
mut last_poll_time := time.now().add_seconds(-d.conf.polling_frequency)
mut sleep_time := 0 * time.second
mut finished, mut empty, mut running := 0, 0, 0
for {
if sleep_time > 0 {
d.ldebug('Sleeping for ${sleep_time}')
time.sleep(sleep_time)
}
finished, empty = d.update_atomics()
running = d.conf.max_concurrent_builds - finished - empty
// No new finished builds and no free slots, so there's nothing to be
// done
if finished + empty == 0 {
sleep_time = 1 * time.second
continue
}
// Builds have finished, so old builder images might have freed up.
// TODO this might query the docker daemon too frequently.
if finished > 0 {
d.images.clean_old_images()
}
// The agent will always poll for new jobs after at most
// `polling_frequency` seconds. However, when jobs have finished, the
// agent will also poll for new jobs. This is because jobs are often
// clustered together (especially when mostly using the global cron
// schedule), so there's a much higher chance jobs are available.
if finished > 0 || time.now() >= last_poll_time.add_seconds(d.conf.polling_frequency) {
d.ldebug('Polling for new jobs')
new_configs := d.client.poll_jobs(d.conf.arch, finished + empty) or {
d.lerror('Failed to poll jobs: ${err.msg()}')
// TODO pick a better delay here
sleep_time = 5 * time.second
continue
}
d.ldebug('Received ${new_configs.len} jobs')
last_poll_time = time.now()
for config in new_configs {
// Make sure a recent build base image is available for
// building the config
if !d.images.up_to_date(config.base_image) {
d.linfo('Building builder image from base image ${config.base_image}')
// TODO handle this better than to just skip the config
d.images.refresh_image(config.base_image) or {
d.lerror(err.msg())
continue
}
}
// It's technically still possible that the build image is
// removed in the very short period between building the
// builder image and starting a build container with it. If
// this happens, fate really just didn't want you to do this
// build.
d.build_channel <- config
running++
}
}
// The agent is not doing anything, so we just wait until the next poll
// time
if running == 0 {
sleep_time = last_poll_time.add_seconds(d.conf.polling_frequency) - time.now()
} else {
sleep_time = 1 * time.second
}
}
}
// update_atomics checks for each build whether it's completed, and sets it to
// empty again if so. The return value is a tuple `(finished, empty)` where
// `finished` is how many builds were just finished and thus set to empty, and
// `empty` is how many build slots were already empty. The amount of running
// builds can then be calculated by substracting these two values from the
// total allowed concurrent builds.
fn (mut d AgentDaemon) update_atomics() (int, int) {
mut finished := 0
mut empty := 0
for i in 0 .. d.atomics.len {
if stdatomic.load_u64(&d.atomics[i]) == agent.build_done {
stdatomic.store_u64(&d.atomics[i], agent.build_empty)
finished++
} else if stdatomic.load_u64(&d.atomics[i]) == agent.build_empty {
empty++
}
}
return finished, empty
}
// run_build actually starts the build process for a given target.
fn (mut d AgentDaemon) run_build(build_index int, config BuildConfig) {
d.linfo('started build: ${config}')
// 0 means success, 1 means failure
mut status := 0
new_config := BuildConfig{
...config
base_image: d.images.get(config.base_image)
}
res := build.build_config(d.client.address, d.client.api_key, new_config) or {
d.ldebug('build_config error: ${err.msg()}')
status = 1
build.BuildResult{}
}
if status == 0 {
d.linfo('Uploading build logs for ${config}')
// TODO use the arch value here
build_arch := os.uname().machine
d.client.add_build_log(config.target_id, res.start_time, res.end_time, build_arch,
res.exit_code, res.logs) or { d.lerror('Failed to upload logs for ${config}') }
} else {
d.lwarn('an error occurred during build: ${config}')
}
stdatomic.store_u64(&d.atomics[build_index], agent.build_done)
}
// builder_thread is a thread that constantly listens for builds to process
fn (mut d AgentDaemon) builder_thread(ch chan BuildConfig, builder_index int) {
for {
build_config := <-ch or { break }
d.run_build(builder_index, build_config)
}
}

View File

@ -1,119 +0,0 @@
module agent
import time
import docker
import build
// An ImageManager is a utility that creates builder images from given base
// images, updating these builder images if they've become too old. This
// structure can manage images from any number of base images, paving the way
// for configurable base images per target/repository.
struct ImageManager {
max_image_age int [required]
mut:
// For each base image, one or more builder images can exist at the same
// time
images map[string][]string [required]
// For each base image, we track when its newest image was built
timestamps map[string]time.Time [required]
}
// new_image_manager initializes a new image manager.
fn new_image_manager(max_image_age int) ImageManager {
return ImageManager{
max_image_age: max_image_age
images: map[string][]string{}
timestamps: map[string]time.Time{}
}
}
// get returns the name of the newest image for the given base image. Note that
// this function should only be called *after* a first call to `refresh_image`.
pub fn (m &ImageManager) get(base_image string) string {
return m.images[base_image].last()
}
// up_to_date returns true if the last known builder image exists and is up to
// date. If this function returns true, the last builder image may be used to
// perform a build.
pub fn (mut m ImageManager) up_to_date(base_image string) bool {
if base_image !in m.timestamps
|| m.timestamps[base_image].add_seconds(m.max_image_age) <= time.now() {
return false
}
// It's possible the image has been removed by some external event, so we
// check whether it actually exists as well.
mut dd := docker.new_conn() or { return false }
defer {
dd.close() or {}
}
dd.image_inspect(m.images[base_image].last()) or {
// Image doesn't exist, so we stop tracking it
if err.code() == 404 {
m.images[base_image].delete_last()
m.timestamps.delete(base_image)
}
// If the inspect fails, it's either because the image doesn't exist or
// because of some other error. Either way, we can't know *for certain*
// that the image exists, so we return false.
return false
}
return true
}
// refresh_image builds a new builder image from the given base image. This
// function should only be called if `up_to_date` returned false.
fn (mut m ImageManager) refresh_image(base_image string) ! {
// TODO use better image tags for built images
new_image := build.create_build_image(base_image) or {
return error('Failed to build builder image from base image ${base_image}')
}
m.images[base_image] << new_image
m.timestamps[base_image] = time.now()
}
// clean_old_images removes all older builder images that are no longer in use.
// The function will always leave at least one builder image, namely the newest
// one.
fn (mut m ImageManager) clean_old_images() {
mut dd := docker.new_conn() or { return }
defer {
dd.close() or {}
}
mut i := 0
for image in m.images.keys() {
i = 0
for i < m.images[image].len - 1 {
// For each builder image, we try to remove it by calling the Docker
// API. If the function returns an error or false, that means the image
// wasn't deleted. Therefore, we move the index over. If the function
// returns true, the array's length has decreased by one so we don't
// move the index.
dd.image_remove(m.images[image][i]) or {
// The image was removed by an external event
if err.code() == 404 {
m.images[image].delete(i)
}
// The image couldn't be removed, so we need to keep track of
// it
else {
i += 1
}
continue
}
m.images[image].delete(i)
}
}
}

View File

@ -1,36 +0,0 @@
module agent
// lfatal create a log message with the fatal level
pub fn (mut d AgentDaemon) lfatal(msg string) {
lock d.logger {
d.logger.fatal(msg)
}
}
// lerror create a log message with the error level
pub fn (mut d AgentDaemon) lerror(msg string) {
lock d.logger {
d.logger.error(msg)
}
}
// lwarn create a log message with the warn level
pub fn (mut d AgentDaemon) lwarn(msg string) {
lock d.logger {
d.logger.warn(msg)
}
}
// linfo create a log message with the info level
pub fn (mut d AgentDaemon) linfo(msg string) {
lock d.logger {
d.logger.info(msg)
}
}
// ldebug create a log message with the debug level
pub fn (mut d AgentDaemon) ldebug(msg string) {
lock d.logger {
d.logger.debug(msg)
}
}

View File

@ -4,7 +4,7 @@
#include "archive.h" #include "archive.h"
pub struct C.archive {} struct C.archive {}
// Create a new archive struct for reading // Create a new archive struct for reading
fn C.archive_read_new() &C.archive fn C.archive_read_new() &C.archive
@ -71,7 +71,7 @@ fn C.archive_filter_code(&C.archive, int) int
#include "archive_entry.h" #include "archive_entry.h"
pub struct C.archive_entry {} struct C.archive_entry {}
// Create a new archive_entry struct // Create a new archive_entry struct
fn C.archive_entry_new() &C.archive_entry fn C.archive_entry_new() &C.archive_entry

View File

@ -3,31 +3,14 @@ module build
import docker import docker
import encoding.base64 import encoding.base64
import time import time
import git
import os import os
import strings
import util
import models { BuildConfig, Target }
const ( const container_build_dir = '/build'
container_build_dir = '/build'
build_image_repo = 'vieter-build'
// Contents of PATH variable in build containers
path_dirs = ['/sbin', '/bin', '/usr/sbin', '/usr/bin', '/usr/local/sbin',
'/usr/local/bin', '/usr/bin/site_perl', '/usr/bin/vendor_perl', '/usr/bin/core_perl']
)
// create_build_image creates a builder image given some base image which can const build_image_repo = 'vieter-build'
// then be used to build & package Arch images. It mostly just updates the
// system, install some necessary packages & creates a non-root user to run
// makepkg with. The base image should be some Linux distribution that uses
// Pacman as its package manager.
pub fn create_build_image(base_image string) !string {
mut dd := docker.new_conn()!
defer {
dd.close() or {}
}
fn create_build_image(base_image string) ?string {
commands := [ commands := [
// Update repos & install required packages // Update repos & install required packages
'pacman -Syu --needed --noconfirm base-devel git' 'pacman -Syu --needed --noconfirm base-devel git'
@ -45,7 +28,7 @@ pub fn create_build_image(base_image string) !string {
c := docker.NewContainer{ c := docker.NewContainer{
image: base_image image: base_image
env: ['BUILD_SCRIPT=${cmds_str}'] env: ['BUILD_SCRIPT=$cmds_str']
entrypoint: ['/bin/sh', '-c'] entrypoint: ['/bin/sh', '-c']
cmd: ['echo \$BUILD_SCRIPT | base64 -d | /bin/sh -e'] cmd: ['echo \$BUILD_SCRIPT | base64 -d | /bin/sh -e']
} }
@ -57,113 +40,94 @@ pub fn create_build_image(base_image string) !string {
image_tag := if image_parts.len > 1 { image_parts[1] } else { 'latest' } image_tag := if image_parts.len > 1 { image_parts[1] } else { 'latest' }
// We pull the provided image // We pull the provided image
dd.image_pull(image_name, image_tag)! docker.pull_image(image_name, image_tag) ?
id := dd.container_create(c)!.id id := docker.create_container(c) ?
// id := docker.create_container(c)! docker.start_container(id) ?
dd.container_start(id)!
// This loop waits until the container has stopped, so we can remove it after // This loop waits until the container has stopped, so we can remove it after
for { for {
data := dd.container_inspect(id)! data := docker.inspect_container(id) ?
if !data.state.running { if !data.state.running {
break break
} }
time.sleep(1 * time.second) // Wait for 5 seconds
time.sleep(5000000000)
} }
// Finally, we create the image from the container // Finally, we create the image from the container
// As the tag, we use the epoch value // As the tag, we use the epoch value
// TODO also add the base image's name into the image name to prevent
// conflicts.
tag := time.sys_mono_now().str() tag := time.sys_mono_now().str()
image := dd.image_from_container(id, 'vieter-build', tag)! image := docker.create_image_from_container(id, 'vieter-build', tag) ?
dd.container_remove(id)! docker.remove_container(id) ?
return image.id return image.id
} }
pub struct BuildResult { fn build(conf Config) ? {
pub: build_arch := os.uname().machine
start_time time.Time
end_time time.Time
exit_code int
logs string
}
// build_target builds the given target. Internally it calls `build_config`. // We get the repos map from the Vieter instance
pub fn build_target(address string, api_key string, base_image_id string, target &Target, force bool, timeout int) !BuildResult { repos_map := git.get_repos(conf.address, conf.api_key) ?
config := target.as_build_config(base_image_id, force, timeout)
return build_config(address, api_key, config) // We filter out any repos that aren't allowed to be built on this
} // architecture
filtered_repos := repos_map.keys().map(repos_map[it]).filter(it.arch.contains(build_arch))
// build_config builds, packages & publishes a given Arch package based on the // No point in doing work if there's no repos present
// provided target. The base image ID should be of an image previously created if filtered_repos.len == 0 {
// by create_build_image. It returns the logs of the container. return
pub fn build_config(address string, api_key string, config BuildConfig) !BuildResult {
mut dd := docker.new_conn()!
defer {
dd.close() or {}
} }
build_arch := os.uname().machine // First, we create a base image which has updated repos n stuff
build_script := create_build_script(address, config, build_arch) image_id := create_build_image(conf.base_image) ?
// We convert the build script into a base64 string, which then gets passed for repo in filtered_repos {
// to the container as an env var // TODO what to do with PKGBUILDs that build multiple packages?
base64_script := base64.encode_str(build_script) commands := [
'git clone --single-branch --depth 1 --branch $repo.branch $repo.url repo',
'cd repo',
'makepkg --nobuild --nodeps',
'source PKGBUILD',
// The build container checks whether the package is already
// present on the server
'curl --head --fail $conf.address/$repo.repo/$build_arch/\$pkgname-\$pkgver-\$pkgrel && exit 0',
'MAKEFLAGS="-j\$(nproc)" makepkg -s --noconfirm --needed && for pkg in \$(ls -1 *.pkg*); do curl -XPOST -T "\$pkg" -H "X-API-KEY: \$API_KEY" $conf.address/$repo.repo/publish; done',
]
// We convert the list of commands into a base64 string, which then gets
// passed to the container as an env var
cmds_str := base64.encode_str(commands.join('\n'))
c := docker.NewContainer{ c := docker.NewContainer{
image: '${config.base_image}' image: '$image_id'
env: [ env: ['BUILD_SCRIPT=$cmds_str', 'API_KEY=$conf.api_key']
'BUILD_SCRIPT=${base64_script}',
'API_KEY=${api_key}',
// `archlinux:base-devel` does not correctly set the path variable,
// causing certain builds to fail. This fixes it.
'PATH=${build.path_dirs.join(':')}',
]
entrypoint: ['/bin/sh', '-c'] entrypoint: ['/bin/sh', '-c']
cmd: ['echo \$BUILD_SCRIPT | base64 -d | /bin/bash -e'] cmd: ['echo \$BUILD_SCRIPT | base64 -d | /bin/bash -e']
work_dir: '/build' work_dir: '/build'
user: '0:0' user: 'builder:builder'
} }
id := dd.container_create(c)!.id id := docker.create_container(c) ?
dd.container_start(id)! docker.start_container(id) ?
mut data := dd.container_inspect(id)!
start_time := time.now()
// This loop waits until the container has stopped, so we can remove it after // This loop waits until the container has stopped, so we can remove it after
for data.state.running { for {
if time.now() - start_time > config.timeout * time.second { data := docker.inspect_container(id) ?
dd.container_kill(id)!
dd.container_remove(id)!
return error('Build killed due to timeout (${config.timeout}s)') if !data.state.running {
break
} }
time.sleep(1 * time.second) // Wait for 5 seconds
time.sleep(5000000000)
data = dd.container_inspect(id)!
} }
mut logs_stream := dd.container_get_logs(id)! docker.remove_container(id) ?
// Read in the entire stream
mut logs_builder := strings.new_builder(10 * 1024)
util.reader_to_writer(mut logs_stream, mut logs_builder)!
dd.container_remove(id)!
return BuildResult{
start_time: data.state.start_time
end_time: data.state.end_time
exit_code: data.state.exit_code
logs: logs_builder.str()
} }
// Finally, we remove the builder image
docker.remove_image(image_id) ?
} }

25
src/build/cli.v 100644
View File

@ -0,0 +1,25 @@
module build
import cli
import env
pub struct Config {
pub:
api_key string
address string
base_image string = 'archlinux:base-devel'
}
// cmd returns the cli submodule that handles the build process
pub fn cmd() cli.Command {
return cli.Command{
name: 'build'
description: 'Run the build process.'
execute: fn (cmd cli.Command) ? {
config_file := cmd.flags.get_string('config-file') ?
conf := env.load<Config>(config_file) ?
build(conf) ?
}
}
}

View File

@ -1,219 +0,0 @@
module build
import models { BuildConfig, Target }
import cron
import time
import datatypes { MinHeap }
import util
struct BuildJob {
pub mut:
// Time at which this build job was created/queued
created time.Time
// Next timestamp from which point this job is allowed to be executed
timestamp time.Time
// Required for calculating next timestamp after having pop'ed a job
ce &cron.Expression = unsafe { nil }
// Actual build config sent to the agent
config BuildConfig
// Whether this is a one-time job
single bool
}
// Allows BuildJob structs to be sorted according to their timestamp in
// MinHeaps
fn (r1 BuildJob) < (r2 BuildJob) bool {
return r1.timestamp < r2.timestamp
}
// The build job queue is responsible for managing the list of scheduled builds
// for each architecture. Agents receive jobs from this queue.
pub struct BuildJobQueue {
// Schedule to use for targets without explicitely defined cron expression
default_schedule &cron.Expression
// Base image to use for targets without defined base image
default_base_image string
// After how many minutes a build should be forcefully cancelled
default_build_timeout int
mut:
mutex shared util.Dummy
// For each architecture, a priority queue is tracked
queues map[string]MinHeap[BuildJob]
// When a target is removed from the server or edited, its previous build
// configs will be invalid. This map allows for those to be simply skipped
// by ignoring any build configs created before this timestamp.
invalidated map[int]time.Time
}
// new_job_queue initializes a new job queue
pub fn new_job_queue(default_schedule &cron.Expression, default_base_image string, default_build_timeout int) BuildJobQueue {
return BuildJobQueue{
default_schedule: unsafe { default_schedule }
default_base_image: default_base_image
default_build_timeout: default_build_timeout
invalidated: map[int]time.Time{}
}
}
// insert_all executes insert for each architecture of the given Target.
pub fn (mut q BuildJobQueue) insert_all(target Target) ! {
for arch in target.arch {
q.insert(target: target, arch: arch.value)!
}
}
[params]
pub struct InsertConfig {
target Target [required]
arch string [required]
single bool
force bool
now bool
}
// insert a new target's job into the queue for the given architecture. This
// job will then be endlessly rescheduled after being pop'ed, unless removed
// explicitely.
pub fn (mut q BuildJobQueue) insert(input InsertConfig) ! {
lock q.mutex {
if input.arch !in q.queues {
q.queues[input.arch] = MinHeap[BuildJob]{}
}
mut job := BuildJob{
created: time.now()
single: input.single
config: input.target.as_build_config(q.default_base_image, input.force, q.default_build_timeout)
}
if !input.now {
ce := if input.target.schedule != '' {
cron.parse_expression(input.target.schedule) or {
return error("Error while parsing cron expression '${input.target.schedule}' (id ${input.target.id}): ${err.msg()}")
}
} else {
q.default_schedule
}
job.timestamp = ce.next_from_now()
job.ce = ce
} else {
job.timestamp = time.now()
}
q.queues[input.arch].insert(job)
}
}
// reschedule the given job by calculating the next timestamp and re-adding it
// to its respective queue. This function is called by the pop functions
// *after* having pop'ed the job.
fn (mut q BuildJobQueue) reschedule(job BuildJob, arch string) {
new_timestamp := job.ce.next_from_now()
new_job := BuildJob{
...job
created: time.now()
timestamp: new_timestamp
}
q.queues[arch].insert(new_job)
}
// pop_invalid pops all invalid jobs.
fn (mut q BuildJobQueue) pop_invalid(arch string) {
for {
job := q.queues[arch].peek() or { return }
if job.config.target_id in q.invalidated
&& job.created < q.invalidated[job.config.target_id] {
// This pop *should* never fail according to the source code
q.queues[arch].pop() or {}
} else {
break
}
}
}
// peek shows the first job for the given architecture that's ready to be
// executed, if present.
pub fn (mut q BuildJobQueue) peek(arch string) ?BuildJob {
// Even peek requires a write lock, because pop_invalid can modify the data
// structure
lock q.mutex {
if arch !in q.queues {
return none
}
q.pop_invalid(arch)
job := q.queues[arch].peek() or { return none }
if job.timestamp < time.now() {
return job
}
}
return none
}
// pop removes the first job for the given architecture that's ready to be
// executed from the queue and returns it, if present.
pub fn (mut q BuildJobQueue) pop(arch string) ?BuildJob {
lock q.mutex {
if arch !in q.queues {
return none
}
q.pop_invalid(arch)
mut job := q.queues[arch].peek() or { return none }
if job.timestamp < time.now() {
job = q.queues[arch].pop() or { return none }
if !job.single {
q.reschedule(job, arch)
}
return job
}
}
return none
}
// pop_n tries to pop at most n available jobs for the given architecture.
pub fn (mut q BuildJobQueue) pop_n(arch string, n int) []BuildJob {
lock q.mutex {
if arch !in q.queues {
return []
}
mut out := []BuildJob{}
for out.len < n {
q.pop_invalid(arch)
mut job := q.queues[arch].peek() or { break }
if job.timestamp < time.now() {
job = q.queues[arch].pop() or { break }
if !job.single {
q.reschedule(job, arch)
}
out << job
} else {
break
}
}
return out
}
return []
}
// invalidate a target's old build jobs.
pub fn (mut q BuildJobQueue) invalidate(target_id int) {
q.invalidated[target_id] = time.now()
}

View File

@ -1,20 +0,0 @@
echo -e '+ echo -e '\''[vieter]\\nServer = https://example.com/$repo/$arch\\nSigLevel = Optional'\'' >> /etc/pacman.conf'
echo -e '[vieter]\nServer = https://example.com/$repo/$arch\nSigLevel = Optional' >> /etc/pacman.conf
echo -e '+ pacman -Syu --needed --noconfirm'
pacman -Syu --needed --noconfirm
echo -e '+ su builder'
su builder
echo -e '+ git clone --single-branch --depth 1 '\''https://examplerepo.com'\'' repo'
git clone --single-branch --depth 1 'https://examplerepo.com' repo
echo -e '+ cd repo'
cd repo
echo -e '+ makepkg --nobuild --syncdeps --needed --noconfirm'
makepkg --nobuild --syncdeps --needed --noconfirm
echo -e '+ source PKGBUILD'
source PKGBUILD
echo -e '+ curl -s --head --fail https://example.com/vieter/x86_64/$pkgname-$pkgver-$pkgrel && exit 0'
curl -s --head --fail https://example.com/vieter/x86_64/$pkgname-$pkgver-$pkgrel && exit 0
echo -e '+ [ "$(id -u)" == 0 ] && exit 0'
[ "$(id -u)" == 0 ] && exit 0
echo -e '+ MAKEFLAGS="-j$(nproc)" makepkg -s --noconfirm --needed --noextract && for pkg in $(ls -1 *.pkg*); do curl -XPOST -T "$pkg" -H "X-API-KEY: $API_KEY" https://example.com/vieter/publish; done'
MAKEFLAGS="-j$(nproc)" makepkg -s --noconfirm --needed --noextract && for pkg in $(ls -1 *.pkg*); do curl -XPOST -T "$pkg" -H "X-API-KEY: $API_KEY" https://example.com/vieter/publish; done

View File

@ -1,20 +0,0 @@
echo -e '+ echo -e '\''[vieter]\\nServer = https://example.com/$repo/$arch\\nSigLevel = Optional'\'' >> /etc/pacman.conf'
echo -e '[vieter]\nServer = https://example.com/$repo/$arch\nSigLevel = Optional' >> /etc/pacman.conf
echo -e '+ pacman -Syu --needed --noconfirm'
pacman -Syu --needed --noconfirm
echo -e '+ su builder'
su builder
echo -e '+ git clone --single-branch --depth 1 --branch main '\''https://examplerepo.com'\'' repo'
git clone --single-branch --depth 1 --branch main 'https://examplerepo.com' repo
echo -e '+ cd repo'
cd repo
echo -e '+ makepkg --nobuild --syncdeps --needed --noconfirm'
makepkg --nobuild --syncdeps --needed --noconfirm
echo -e '+ source PKGBUILD'
source PKGBUILD
echo -e '+ curl -s --head --fail https://example.com/vieter/x86_64/$pkgname-$pkgver-$pkgrel && exit 0'
curl -s --head --fail https://example.com/vieter/x86_64/$pkgname-$pkgver-$pkgrel && exit 0
echo -e '+ [ "$(id -u)" == 0 ] && exit 0'
[ "$(id -u)" == 0 ] && exit 0
echo -e '+ MAKEFLAGS="-j$(nproc)" makepkg -s --noconfirm --needed --noextract && for pkg in $(ls -1 *.pkg*); do curl -XPOST -T "$pkg" -H "X-API-KEY: $API_KEY" https://example.com/vieter/publish; done'
MAKEFLAGS="-j$(nproc)" makepkg -s --noconfirm --needed --noextract && for pkg in $(ls -1 *.pkg*); do curl -XPOST -T "$pkg" -H "X-API-KEY: $API_KEY" https://example.com/vieter/publish; done

View File

@ -1,20 +0,0 @@
echo -e '+ echo -e '\''[vieter]\\nServer = https://example.com/$repo/$arch\\nSigLevel = Optional'\'' >> /etc/pacman.conf'
echo -e '[vieter]\nServer = https://example.com/$repo/$arch\nSigLevel = Optional' >> /etc/pacman.conf
echo -e '+ pacman -Syu --needed --noconfirm'
pacman -Syu --needed --noconfirm
echo -e '+ su builder'
su builder
echo -e '+ git clone --single-branch --depth 1 '\''https://examplerepo.com'\'' repo'
git clone --single-branch --depth 1 'https://examplerepo.com' repo
echo -e '+ cd '\''repo/example/path'\'''
cd 'repo/example/path'
echo -e '+ makepkg --nobuild --syncdeps --needed --noconfirm'
makepkg --nobuild --syncdeps --needed --noconfirm
echo -e '+ source PKGBUILD'
source PKGBUILD
echo -e '+ curl -s --head --fail https://example.com/vieter/x86_64/$pkgname-$pkgver-$pkgrel && exit 0'
curl -s --head --fail https://example.com/vieter/x86_64/$pkgname-$pkgver-$pkgrel && exit 0
echo -e '+ [ "$(id -u)" == 0 ] && exit 0'
[ "$(id -u)" == 0 ] && exit 0
echo -e '+ MAKEFLAGS="-j$(nproc)" makepkg -s --noconfirm --needed --noextract && for pkg in $(ls -1 *.pkg*); do curl -XPOST -T "$pkg" -H "X-API-KEY: $API_KEY" https://example.com/vieter/publish; done'
MAKEFLAGS="-j$(nproc)" makepkg -s --noconfirm --needed --noextract && for pkg in $(ls -1 *.pkg*); do curl -XPOST -T "$pkg" -H "X-API-KEY: $API_KEY" https://example.com/vieter/publish; done

View File

@ -1,20 +0,0 @@
echo -e '+ echo -e '\''[vieter]\\nServer = https://example.com/$repo/$arch\\nSigLevel = Optional'\'' >> /etc/pacman.conf'
echo -e '[vieter]\nServer = https://example.com/$repo/$arch\nSigLevel = Optional' >> /etc/pacman.conf
echo -e '+ pacman -Syu --needed --noconfirm'
pacman -Syu --needed --noconfirm
echo -e '+ su builder'
su builder
echo -e '+ git clone --single-branch --depth 1 '\''https://examplerepo.com'\'' repo'
git clone --single-branch --depth 1 'https://examplerepo.com' repo
echo -e '+ cd '\''repo/example/path with spaces'\'''
cd 'repo/example/path with spaces'
echo -e '+ makepkg --nobuild --syncdeps --needed --noconfirm'
makepkg --nobuild --syncdeps --needed --noconfirm
echo -e '+ source PKGBUILD'
source PKGBUILD
echo -e '+ curl -s --head --fail https://example.com/vieter/x86_64/$pkgname-$pkgver-$pkgrel && exit 0'
curl -s --head --fail https://example.com/vieter/x86_64/$pkgname-$pkgver-$pkgrel && exit 0
echo -e '+ [ "$(id -u)" == 0 ] && exit 0'
[ "$(id -u)" == 0 ] && exit 0
echo -e '+ MAKEFLAGS="-j$(nproc)" makepkg -s --noconfirm --needed --noextract && for pkg in $(ls -1 *.pkg*); do curl -XPOST -T "$pkg" -H "X-API-KEY: $API_KEY" https://example.com/vieter/publish; done'
MAKEFLAGS="-j$(nproc)" makepkg -s --noconfirm --needed --noextract && for pkg in $(ls -1 *.pkg*); do curl -XPOST -T "$pkg" -H "X-API-KEY: $API_KEY" https://example.com/vieter/publish; done

View File

@ -1,22 +0,0 @@
echo -e '+ echo -e '\''[vieter]\\nServer = https://example.com/$repo/$arch\\nSigLevel = Optional'\'' >> /etc/pacman.conf'
echo -e '[vieter]\nServer = https://example.com/$repo/$arch\nSigLevel = Optional' >> /etc/pacman.conf
echo -e '+ pacman -Syu --needed --noconfirm'
pacman -Syu --needed --noconfirm
echo -e '+ su builder'
su builder
echo -e '+ mkdir repo'
mkdir repo
echo -e '+ curl -o repo/PKGBUILD -L '\''https://examplerepo.com'\'''
curl -o repo/PKGBUILD -L 'https://examplerepo.com'
echo -e '+ cd repo'
cd repo
echo -e '+ makepkg --nobuild --syncdeps --needed --noconfirm'
makepkg --nobuild --syncdeps --needed --noconfirm
echo -e '+ source PKGBUILD'
source PKGBUILD
echo -e '+ curl -s --head --fail https://example.com/vieter/x86_64/$pkgname-$pkgver-$pkgrel && exit 0'
curl -s --head --fail https://example.com/vieter/x86_64/$pkgname-$pkgver-$pkgrel && exit 0
echo -e '+ [ "$(id -u)" == 0 ] && exit 0'
[ "$(id -u)" == 0 ] && exit 0
echo -e '+ MAKEFLAGS="-j$(nproc)" makepkg -s --noconfirm --needed --noextract && for pkg in $(ls -1 *.pkg*); do curl -XPOST -T "$pkg" -H "X-API-KEY: $API_KEY" https://example.com/vieter/publish; done'
MAKEFLAGS="-j$(nproc)" makepkg -s --noconfirm --needed --noextract && for pkg in $(ls -1 *.pkg*); do curl -XPOST -T "$pkg" -H "X-API-KEY: $API_KEY" https://example.com/vieter/publish; done

View File

@ -1,93 +0,0 @@
module build
import models { BuildConfig }
// escape_shell_string escapes any characters that could be interpreted
// incorrectly by a shell. The resulting value should be safe to use inside an
// echo statement.
fn escape_shell_string(s string) string {
return s.replace(r'\', r'\\').replace("'", r"'\''")
}
// echo_commands takes a list of shell commands & prepends each one with
// an echo call displaying said command.
pub fn echo_commands(cmds []string) []string {
mut out := []string{cap: 2 * cmds.len}
for cmd in cmds {
out << "echo -e '+ ${escape_shell_string(cmd)}'"
out << cmd
}
return out
}
// create_build_script generates a shell script that builds a given Target.
fn create_build_script(address string, config BuildConfig, build_arch string) string {
repo_url := '${address}/${config.repo}'
mut commands := [
// This will later be replaced by a proper setting for changing the
// mirrorlist
"echo -e '[${config.repo}]\\nServer = ${address}/\$repo/\$arch\\nSigLevel = Optional' >> /etc/pacman.conf"
// We need to update the package list of the repo we just added above.
// This should however not pull in a lot of packages as long as the
// builder image is rebuilt frequently.
'pacman -Syu --needed --noconfirm',
// makepkg can't run as root
'su builder',
]
commands << match config.kind {
'git' {
if config.branch == '' {
[
"git clone --single-branch --depth 1 '${config.url}' repo",
]
} else {
[
"git clone --single-branch --depth 1 --branch ${config.branch} '${config.url}' repo",
]
}
}
'url' {
[
'mkdir repo',
"curl -o repo/PKGBUILD -L '${config.url}'",
]
}
else {
panic("Invalid kind. This shouldn't be possible.")
}
}
commands << if config.path != '' {
"cd 'repo/${config.path}'"
} else {
'cd repo'
}
commands << [
'makepkg --nobuild --syncdeps --needed --noconfirm',
'source PKGBUILD',
]
if !config.force {
// The build container checks whether the package is already present on
// the server.
commands << [
'curl -s --head --fail ${repo_url}/${build_arch}/\$pkgname-\$pkgver-\$pkgrel && exit 0',
// If the above curl command succeeds, we don't need to rebuild the
// package. However, because we're in a su shell, the exit command will
// drop us back into the root shell. Therefore, we must check whether
// we're in root so we don't proceed.
'[ "\$(id -u)" == 0 ] && exit 0',
]
}
commands << [
'MAKEFLAGS="-j\$(nproc)" makepkg -s --noconfirm --needed --noextract && for pkg in \$(ls -1 *.pkg*); do curl -XPOST -T "\$pkg" -H "X-API-KEY: \$API_KEY" ${repo_url}/publish; done',
]
return echo_commands(commands).join('\n')
}

Some files were not shown because too many files have changed in this diff Show More