diff --git a/.dockerignore b/.dockerignore index 3909ae0..643e6db 100644 --- a/.dockerignore +++ b/.dockerignore @@ -2,4 +2,3 @@ !src/ !Makefile -!patches/ diff --git a/.editorconfig b/.editorconfig deleted file mode 100644 index 355c6bf..0000000 --- a/.editorconfig +++ /dev/null @@ -1,10 +0,0 @@ -# top-most EditorConfig file -root = true - -# Unix-style newlines with a newline ending every file -[*] -end_of_line = lf -insert_final_newline = true - -[*.v] -indent_style = space diff --git a/.gitignore b/.gitignore index df4fcc9..8c67f97 100644 --- a/.gitignore +++ b/.gitignore @@ -14,7 +14,3 @@ vieter.log # External lib; gets added by Makefile libarchive-* -test/ - -# V compiler directory -v-*/ diff --git a/.woodpecker/.build.yml b/.woodpecker/.build.yml index 66caa8c..40a50e1 100644 --- a/.woodpecker/.build.yml +++ b/.woodpecker/.build.yml @@ -1,62 +1,18 @@ -matrix: - PLATFORM: - - linux/amd64 - - linux/arm64 - - linux/arm/v7 - -# These checks already get performed on the feature branches -platform: ${PLATFORM} - pipeline: - # The default build isn't needed, as alpine switches to gcc for the compiler anyways + vieter: + image: 'chewingbever/vlang:latest' + group: 'build' + commands: + - make vieter + debug: image: 'chewingbever/vlang:latest' - pull: true group: 'build' commands: - make debug - when: - event: push prod: image: 'chewingbever/vlang:latest' - pull: true - environment: - - LDFLAGS=-lz -lbz2 -llzma -lexpat -lzstd -llz4 -static group: 'build' commands: - make prod - # Make sure the binary is actually statically built - - readelf -d pvieter - - du -h pvieter - - '[ "$(readelf -d pvieter | grep NEEDED | wc -l)" = 0 ]' - # This removes so much, it's amazing - - strip -s pvieter - - du -h pvieter - when: - event: push - - upload: - image: 'chewingbever/vlang:latest' - secrets: [ s3_username, s3_password ] - commands: - # https://gist.github.com/JustinTimperio/7c7115f87b775618637d67ac911e595f - - export URL=s3.rustybever.be - - export OBJ_PATH="/vieter/commits/$CI_COMMIT_SHA/vieter-$(echo '${PLATFORM}' | sed 's:/:-:g')" - - export DATE="$(date -R --utc)" - - export CONTENT_TYPE='application/zstd' - - export SIG_STRING="PUT\n\n$CONTENT_TYPE\n$DATE\n$OBJ_PATH" - - export SIGNATURE=`echo -en $SIG_STRING | openssl sha1 -hmac $S3_PASSWORD -binary | base64` - - - > - curl - --silent - -XPUT - -T pvieter - -H "Host: $URL" - -H "Date: $DATE" - -H "Content-Type: $CONTENT_TYPE" - -H "Authorization: AWS $S3_USERNAME:$SIGNATURE" - https://$URL$OBJ_PATH - when: - event: push diff --git a/.woodpecker/.builder.yml b/.woodpecker/.builder.yml deleted file mode 100644 index e94a846..0000000 --- a/.woodpecker/.builder.yml +++ /dev/null @@ -1,18 +0,0 @@ -branches: dev -platform: linux/amd64 - -pipeline: - publish: - image: woodpeckerci/plugin-docker-buildx - secrets: [ docker_username, docker_password ] - settings: - repo: chewingbever/vlang - tag: latest - dockerfile: Dockerfile.builder - platforms: [ linux/arm/v7, linux/arm64/v8, linux/amd64 ] - when: - event: push - path: - - Makefile - - Dockerfile.builder - - patches/* diff --git a/.woodpecker/.docker.yml b/.woodpecker/.docker.yml deleted file mode 100644 index f23b3df..0000000 --- a/.woodpecker/.docker.yml +++ /dev/null @@ -1,34 +0,0 @@ -branches: [main, dev] -platform: linux/amd64 -depends_on: - - builder - - build - -pipeline: - dev: - image: woodpeckerci/plugin-docker-buildx - secrets: [ docker_username, docker_password ] - settings: - repo: chewingbever/vieter - dockerfile: Dockerfile.ci - tag: dev - platforms: [ linux/arm/v7, 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 - dockerfile: Dockerfile.ci - auto_tag: true - platforms: [ linux/arm/v7, linux/arm64/v8, linux/amd64 ] - build_args_from_env: - - CI_COMMIT_SHA - when: - event: tag - branch: main diff --git a/.woodpecker/.gitea.yml b/.woodpecker/.gitea.yml deleted file mode 100644 index e6062eb..0000000 --- a/.woodpecker/.gitea.yml +++ /dev/null @@ -1,34 +0,0 @@ -# Yeah so this only works on tags so we'll worry about this later -platform: linux/amd64 -branches: main -depends_on: - - build - -# We need the entire repo in order for the release names to work -skip_clone: true - -pipeline: - prepare: - image: 'chewingbever/vlang:latest' - pull: true - secrets: [ s3_username, s3_password ] - commands: - - mc alias set s3/ https://s3.rustybever.be "$S3_USERNAME" "$S3_PASSWORD" - - mc cp -r "s3/vieter/commits/$CI_COMMIT_SHA" assets - when: - event: tag - - release: - image: 'plugins/gitea-release' - secrets: - - gitea_release_api_key - settings: - base_url: https://git.rustybever.be - files: assets/* - checksums: - - md5 - - sha256 - prerelease: true - title: ${CI_COMMIT_TAG} - when: - event: tag diff --git a/.woodpecker/.lint.yml b/.woodpecker/.lint.yml index ce000cd..c72c13a 100644 --- a/.woodpecker/.lint.yml +++ b/.woodpecker/.lint.yml @@ -1,13 +1,5 @@ -# 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 diff --git a/.woodpecker/.publish.yml b/.woodpecker/.publish.yml new file mode 100644 index 0000000..1e52db3 --- /dev/null +++ b/.woodpecker/.publish.yml @@ -0,0 +1,29 @@ +pipeline: + dev: + image: plugins/docker + secrets: [ docker_username, docker_password ] + settings: + repo: chewingbever/vieter + tag: dev + when: + event: push + branch: dev + + release: + image: plugins/docker + secrets: [ docker_username, docker_password ] + settings: + repo: chewingbever/vieter + tag: + - latest + - $CI_COMMIT_TAG + mtu: 1000 + when: + event: tag + branch: main + +branches: [main, dev] + +depends_on: + - lint + - build diff --git a/CHANGELOG.md b/CHANGELOG.md deleted file mode 100644 index 017cbff..0000000 --- a/CHANGELOG.md +++ /dev/null @@ -1,13 +0,0 @@ -# Changelog - -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/), -and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). - -## [Unreleased](https://git.rustybever.be/Chewing_Bever/vieter) - -### Added - -* Ability to publish packages -* Re-wrote repo-add in V diff --git a/Dockerfile b/Dockerfile index 5564e34..556ca58 100644 --- a/Dockerfile +++ b/Dockerfile @@ -5,15 +5,17 @@ WORKDIR /app # Copy over source code & build production binary COPY src ./src COPY Makefile ./ - -ENV LDFLAGS='-lz -lbz2 -llzma -lexpat -lzstd -llz4 -static' -RUN v -o pvieter -cflags "-O3" src +RUN make prod FROM alpine:3.15 ENV REPO_DIR=/data +RUN apk update && \ + apk add --no-cache \ + libarchive + COPY --from=builder /app/pvieter /usr/local/bin/vieter ENTRYPOINT [ "/usr/local/bin/vieter" ] diff --git a/Dockerfile.builder b/Dockerfile.builder deleted file mode 100644 index 3e40d89..0000000 --- a/Dockerfile.builder +++ /dev/null @@ -1,35 +0,0 @@ -FROM alpine:3.12 - -ARG TARGETPLATFORM - -WORKDIR /opt/vlang - -ENV VVV /opt/vlang -ENV PATH /opt/vlang:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin -ENV VFLAGS -cc gcc -ENV V_PATH /opt/vlang/v - -RUN ln -s /opt/vlang/v /usr/bin/v && \ - apk --no-cache add \ - git make gcc curl openssl \ - musl-dev \ - openssl-libs-static openssl-dev \ - zlib-static bzip2-static xz-dev expat-static zstd-static lz4-static \ - sqlite-static sqlite-dev \ - libx11-dev glfw-dev freetype-dev \ - libarchive-static libarchive-dev \ - diffutils - -COPY patches ./patches -COPY Makefile ./ - -RUN make v && \ - mv v-*/* /opt/vlang && \ - v -version - -RUN if [ "$TARGETPLATFORM" = 'linux/amd64' ]; then \ - wget -O /usr/local/bin/mc https://dl.min.io/client/mc/release/linux-amd64/mc && \ - chmod +x /usr/local/bin/mc ; \ -fi - -CMD ["v"] diff --git a/Dockerfile.ci b/Dockerfile.ci deleted file mode 100644 index 24f2bef..0000000 --- a/Dockerfile.ci +++ /dev/null @@ -1,46 +0,0 @@ -# vim: ft=dockerfile -# This image just has the required tools to download the binaries -FROM chewingbever/vlang:latest AS builder - -ARG TARGETPLATFORM -ARG CI_COMMIT_SHA -ARG DI_VER=1.2.5 - -WORKDIR /app - -# Build dumb-init -RUN curl -Lo - "https://github.com/Yelp/dumb-init/archive/refs/tags/v${DI_VER}.tar.gz" | tar -xzf - && \ - cd "dumb-init-${DI_VER}" && \ - make SHELL=/bin/sh && \ - mv dumb-init .. && \ - cd .. - -RUN curl --fail \ - -o vieter \ - "https://s3.rustybever.be/vieter/commits/${CI_COMMIT_SHA}/vieter-$(echo "${TARGETPLATFORM}" | sed 's:/:-:g')" && \ - chmod +x vieter - - -FROM busybox:1.35.0 - -ENV PATH=/bin \ - REPO_DIR=/data/repo \ - PKG_DIR=/data/pkgs \ - DOWNLOAD_DIR=/data/downloads - -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 && \ - chown -R www-data:www-data /data - -WORKDIR /data - -USER www-data:www-data - -ENTRYPOINT ["/bin/dumb-init", "--"] -CMD ["/bin/vieter"] diff --git a/LICENSE b/LICENSE deleted file mode 100644 index 0ad25db..0000000 --- a/LICENSE +++ /dev/null @@ -1,661 +0,0 @@ - GNU AFFERO GENERAL PUBLIC LICENSE - Version 3, 19 November 2007 - - Copyright (C) 2007 Free Software Foundation, Inc. - Everyone is permitted to copy and distribute verbatim copies - of this license document, but changing it is not allowed. - - Preamble - - The GNU Affero General Public License is a free, copyleft license for -software and other kinds of works, specifically designed to ensure -cooperation with the community in the case of network server software. - - The licenses for most software and other practical works are designed -to take away your freedom to share and change the works. By contrast, -our General Public Licenses are intended to guarantee your freedom to -share and change all versions of a program--to make sure it remains free -software for all its users. - - When we speak of free software, we are referring to freedom, not -price. Our General Public Licenses are designed to make sure that you -have the freedom to distribute copies of free software (and charge for -them if you wish), that you receive source code or can get it if you -want it, that you can change the software or use pieces of it in new -free programs, and that you know you can do these things. - - Developers that use our General Public Licenses protect your rights -with two steps: (1) assert copyright on the software, and (2) offer -you this License which gives you legal permission to copy, distribute -and/or modify the software. - - A secondary benefit of defending all users' freedom is that -improvements made in alternate versions of the program, if they -receive widespread use, become available for other developers to -incorporate. Many developers of free software are heartened and -encouraged by the resulting cooperation. However, in the case of -software used on network servers, this result may fail to come about. -The GNU General Public License permits making a modified version and -letting the public access it on a server without ever releasing its -source code to the public. - - The GNU Affero General Public License is designed specifically to -ensure that, in such cases, the modified source code becomes available -to the community. It requires the operator of a network server to -provide the source code of the modified version running there to the -users of that server. Therefore, public use of a modified version, on -a publicly accessible server, gives the public access to the source -code of the modified version. - - An older license, called the Affero General Public License and -published by Affero, was designed to accomplish similar goals. This is -a different license, not a version of the Affero GPL, but Affero has -released a new version of the Affero GPL which permits relicensing under -this license. - - The precise terms and conditions for copying, distribution and -modification follow. - - TERMS AND CONDITIONS - - 0. Definitions. - - "This License" refers to version 3 of the GNU Affero General Public License. - - "Copyright" also means copyright-like laws that apply to other kinds of -works, such as semiconductor masks. - - "The Program" refers to any copyrightable work licensed under this -License. Each licensee is addressed as "you". "Licensees" and -"recipients" may be individuals or organizations. - - To "modify" a work means to copy from or adapt all or part of the work -in a fashion requiring copyright permission, other than the making of an -exact copy. The resulting work is called a "modified version" of the -earlier work or a work "based on" the earlier work. - - A "covered work" means either the unmodified Program or a work based -on the Program. - - To "propagate" a work means to do anything with it that, without -permission, would make you directly or secondarily liable for -infringement under applicable copyright law, except executing it on a -computer or modifying a private copy. Propagation includes copying, -distribution (with or without modification), making available to the -public, and in some countries other activities as well. - - To "convey" a work means any kind of propagation that enables other -parties to make or receive copies. Mere interaction with a user through -a computer network, with no transfer of a copy, is not conveying. - - An interactive user interface displays "Appropriate Legal Notices" -to the extent that it includes a convenient and prominently visible -feature that (1) displays an appropriate copyright notice, and (2) -tells the user that there is no warranty for the work (except to the -extent that warranties are provided), that licensees may convey the -work under this License, and how to view a copy of this License. If -the interface presents a list of user commands or options, such as a -menu, a prominent item in the list meets this criterion. - - 1. Source Code. - - The "source code" for a work means the preferred form of the work -for making modifications to it. "Object code" means any non-source -form of a work. - - A "Standard Interface" means an interface that either is an official -standard defined by a recognized standards body, or, in the case of -interfaces specified for a particular programming language, one that -is widely used among developers working in that language. - - The "System Libraries" of an executable work include anything, other -than the work as a whole, that (a) is included in the normal form of -packaging a Major Component, but which is not part of that Major -Component, and (b) serves only to enable use of the work with that -Major Component, or to implement a Standard Interface for which an -implementation is available to the public in source code form. A -"Major Component", in this context, means a major essential component -(kernel, window system, and so on) of the specific operating system -(if any) on which the executable work runs, or a compiler used to -produce the work, or an object code interpreter used to run it. - - The "Corresponding Source" for a work in object code form means all -the source code needed to generate, install, and (for an executable -work) run the object code and to modify the work, including scripts to -control those activities. However, it does not include the work's -System Libraries, or general-purpose tools or generally available free -programs which are used unmodified in performing those activities but -which are not part of the work. For example, Corresponding Source -includes interface definition files associated with source files for -the work, and the source code for shared libraries and dynamically -linked subprograms that the work is specifically designed to require, -such as by intimate data communication or control flow between those -subprograms and other parts of the work. - - The Corresponding Source need not include anything that users -can regenerate automatically from other parts of the Corresponding -Source. - - The Corresponding Source for a work in source code form is that -same work. - - 2. Basic Permissions. - - All rights granted under this License are granted for the term of -copyright on the Program, and are irrevocable provided the stated -conditions are met. This License explicitly affirms your unlimited -permission to run the unmodified Program. The output from running a -covered work is covered by this License only if the output, given its -content, constitutes a covered work. This License acknowledges your -rights of fair use or other equivalent, as provided by copyright law. - - You may make, run and propagate covered works that you do not -convey, without conditions so long as your license otherwise remains -in force. You may convey covered works to others for the sole purpose -of having them make modifications exclusively for you, or provide you -with facilities for running those works, provided that you comply with -the terms of this License in conveying all material for which you do -not control copyright. Those thus making or running the covered works -for you must do so exclusively on your behalf, under your direction -and control, on terms that prohibit them from making any copies of -your copyrighted material outside their relationship with you. - - Conveying under any other circumstances is permitted solely under -the conditions stated below. Sublicensing is not allowed; section 10 -makes it unnecessary. - - 3. Protecting Users' Legal Rights From Anti-Circumvention Law. - - No covered work shall be deemed part of an effective technological -measure under any applicable law fulfilling obligations under article -11 of the WIPO copyright treaty adopted on 20 December 1996, or -similar laws prohibiting or restricting circumvention of such -measures. - - When you convey a covered work, you waive any legal power to forbid -circumvention of technological measures to the extent such circumvention -is effected by exercising rights under this License with respect to -the covered work, and you disclaim any intention to limit operation or -modification of the work as a means of enforcing, against the work's -users, your or third parties' legal rights to forbid circumvention of -technological measures. - - 4. Conveying Verbatim Copies. - - You may convey verbatim copies of the Program's source code as you -receive it, in any medium, provided that you conspicuously and -appropriately publish on each copy an appropriate copyright notice; -keep intact all notices stating that this License and any -non-permissive terms added in accord with section 7 apply to the code; -keep intact all notices of the absence of any warranty; and give all -recipients a copy of this License along with the Program. - - You may charge any price or no price for each copy that you convey, -and you may offer support or warranty protection for a fee. - - 5. Conveying Modified Source Versions. - - You may convey a work based on the Program, or the modifications to -produce it from the Program, in the form of source code under the -terms of section 4, provided that you also meet all of these conditions: - - a) The work must carry prominent notices stating that you modified - it, and giving a relevant date. - - b) The work must carry prominent notices stating that it is - released under this License and any conditions added under section - 7. This requirement modifies the requirement in section 4 to - "keep intact all notices". - - c) You must license the entire work, as a whole, under this - License to anyone who comes into possession of a copy. This - License will therefore apply, along with any applicable section 7 - additional terms, to the whole of the work, and all its parts, - regardless of how they are packaged. This License gives no - permission to license the work in any other way, but it does not - invalidate such permission if you have separately received it. - - d) If the work has interactive user interfaces, each must display - Appropriate Legal Notices; however, if the Program has interactive - interfaces that do not display Appropriate Legal Notices, your - work need not make them do so. - - A compilation of a covered work with other separate and independent -works, which are not by their nature extensions of the covered work, -and which are not combined with it such as to form a larger program, -in or on a volume of a storage or distribution medium, is called an -"aggregate" if the compilation and its resulting copyright are not -used to limit the access or legal rights of the compilation's users -beyond what the individual works permit. Inclusion of a covered work -in an aggregate does not cause this License to apply to the other -parts of the aggregate. - - 6. Conveying Non-Source Forms. - - You may convey a covered work in object code form under the terms -of sections 4 and 5, provided that you also convey the -machine-readable Corresponding Source under the terms of this License, -in one of these ways: - - a) Convey the object code in, or embodied in, a physical product - (including a physical distribution medium), accompanied by the - Corresponding Source fixed on a durable physical medium - customarily used for software interchange. - - b) Convey the object code in, or embodied in, a physical product - (including a physical distribution medium), accompanied by a - written offer, valid for at least three years and valid for as - long as you offer spare parts or customer support for that product - model, to give anyone who possesses the object code either (1) a - copy of the Corresponding Source for all the software in the - product that is covered by this License, on a durable physical - medium customarily used for software interchange, for a price no - more than your reasonable cost of physically performing this - conveying of source, or (2) access to copy the - Corresponding Source from a network server at no charge. - - c) Convey individual copies of the object code with a copy of the - written offer to provide the Corresponding Source. This - alternative is allowed only occasionally and noncommercially, and - only if you received the object code with such an offer, in accord - with subsection 6b. - - d) Convey the object code by offering access from a designated - place (gratis or for a charge), and offer equivalent access to the - Corresponding Source in the same way through the same place at no - further charge. You need not require recipients to copy the - Corresponding Source along with the object code. If the place to - copy the object code is a network server, the Corresponding Source - may be on a different server (operated by you or a third party) - that supports equivalent copying facilities, provided you maintain - clear directions next to the object code saying where to find the - Corresponding Source. Regardless of what server hosts the - Corresponding Source, you remain obligated to ensure that it is - available for as long as needed to satisfy these requirements. - - e) Convey the object code using peer-to-peer transmission, provided - you inform other peers where the object code and Corresponding - Source of the work are being offered to the general public at no - charge under subsection 6d. - - A separable portion of the object code, whose source code is excluded -from the Corresponding Source as a System Library, need not be -included in conveying the object code work. - - A "User Product" is either (1) a "consumer product", which means any -tangible personal property which is normally used for personal, family, -or household purposes, or (2) anything designed or sold for incorporation -into a dwelling. In determining whether a product is a consumer product, -doubtful cases shall be resolved in favor of coverage. For a particular -product received by a particular user, "normally used" refers to a -typical or common use of that class of product, regardless of the status -of the particular user or of the way in which the particular user -actually uses, or expects or is expected to use, the product. A product -is a consumer product regardless of whether the product has substantial -commercial, industrial or non-consumer uses, unless such uses represent -the only significant mode of use of the product. - - "Installation Information" for a User Product means any methods, -procedures, authorization keys, or other information required to install -and execute modified versions of a covered work in that User Product from -a modified version of its Corresponding Source. The information must -suffice to ensure that the continued functioning of the modified object -code is in no case prevented or interfered with solely because -modification has been made. - - If you convey an object code work under this section in, or with, or -specifically for use in, a User Product, and the conveying occurs as -part of a transaction in which the right of possession and use of the -User Product is transferred to the recipient in perpetuity or for a -fixed term (regardless of how the transaction is characterized), the -Corresponding Source conveyed under this section must be accompanied -by the Installation Information. But this requirement does not apply -if neither you nor any third party retains the ability to install -modified object code on the User Product (for example, the work has -been installed in ROM). - - The requirement to provide Installation Information does not include a -requirement to continue to provide support service, warranty, or updates -for a work that has been modified or installed by the recipient, or for -the User Product in which it has been modified or installed. Access to a -network may be denied when the modification itself materially and -adversely affects the operation of the network or violates the rules and -protocols for communication across the network. - - Corresponding Source conveyed, and Installation Information provided, -in accord with this section must be in a format that is publicly -documented (and with an implementation available to the public in -source code form), and must require no special password or key for -unpacking, reading or copying. - - 7. Additional Terms. - - "Additional permissions" are terms that supplement the terms of this -License by making exceptions from one or more of its conditions. -Additional permissions that are applicable to the entire Program shall -be treated as though they were included in this License, to the extent -that they are valid under applicable law. If additional permissions -apply only to part of the Program, that part may be used separately -under those permissions, but the entire Program remains governed by -this License without regard to the additional permissions. - - When you convey a copy of a covered work, you may at your option -remove any additional permissions from that copy, or from any part of -it. (Additional permissions may be written to require their own -removal in certain cases when you modify the work.) You may place -additional permissions on material, added by you to a covered work, -for which you have or can give appropriate copyright permission. - - Notwithstanding any other provision of this License, for material you -add to a covered work, you may (if authorized by the copyright holders of -that material) supplement the terms of this License with terms: - - a) Disclaiming warranty or limiting liability differently from the - terms of sections 15 and 16 of this License; or - - b) Requiring preservation of specified reasonable legal notices or - author attributions in that material or in the Appropriate Legal - Notices displayed by works containing it; or - - c) Prohibiting misrepresentation of the origin of that material, or - requiring that modified versions of such material be marked in - reasonable ways as different from the original version; or - - d) Limiting the use for publicity purposes of names of licensors or - authors of the material; or - - e) Declining to grant rights under trademark law for use of some - trade names, trademarks, or service marks; or - - f) Requiring indemnification of licensors and authors of that - material by anyone who conveys the material (or modified versions of - it) with contractual assumptions of liability to the recipient, for - any liability that these contractual assumptions directly impose on - those licensors and authors. - - All other non-permissive additional terms are considered "further -restrictions" within the meaning of section 10. If the Program as you -received it, or any part of it, contains a notice stating that it is -governed by this License along with a term that is a further -restriction, you may remove that term. If a license document contains -a further restriction but permits relicensing or conveying under this -License, you may add to a covered work material governed by the terms -of that license document, provided that the further restriction does -not survive such relicensing or conveying. - - If you add terms to a covered work in accord with this section, you -must place, in the relevant source files, a statement of the -additional terms that apply to those files, or a notice indicating -where to find the applicable terms. - - Additional terms, permissive or non-permissive, may be stated in the -form of a separately written license, or stated as exceptions; -the above requirements apply either way. - - 8. Termination. - - You may not propagate or modify a covered work except as expressly -provided under this License. Any attempt otherwise to propagate or -modify it is void, and will automatically terminate your rights under -this License (including any patent licenses granted under the third -paragraph of section 11). - - However, if you cease all violation of this License, then your -license from a particular copyright holder is reinstated (a) -provisionally, unless and until the copyright holder explicitly and -finally terminates your license, and (b) permanently, if the copyright -holder fails to notify you of the violation by some reasonable means -prior to 60 days after the cessation. - - Moreover, your license from a particular copyright holder is -reinstated permanently if the copyright holder notifies you of the -violation by some reasonable means, this is the first time you have -received notice of violation of this License (for any work) from that -copyright holder, and you cure the violation prior to 30 days after -your receipt of the notice. - - Termination of your rights under this section does not terminate the -licenses of parties who have received copies or rights from you under -this License. If your rights have been terminated and not permanently -reinstated, you do not qualify to receive new licenses for the same -material under section 10. - - 9. Acceptance Not Required for Having Copies. - - You are not required to accept this License in order to receive or -run a copy of the Program. Ancillary propagation of a covered work -occurring solely as a consequence of using peer-to-peer transmission -to receive a copy likewise does not require acceptance. However, -nothing other than this License grants you permission to propagate or -modify any covered work. These actions infringe copyright if you do -not accept this License. Therefore, by modifying or propagating a -covered work, you indicate your acceptance of this License to do so. - - 10. Automatic Licensing of Downstream Recipients. - - Each time you convey a covered work, the recipient automatically -receives a license from the original licensors, to run, modify and -propagate that work, subject to this License. You are not responsible -for enforcing compliance by third parties with this License. - - An "entity transaction" is a transaction transferring control of an -organization, or substantially all assets of one, or subdividing an -organization, or merging organizations. If propagation of a covered -work results from an entity transaction, each party to that -transaction who receives a copy of the work also receives whatever -licenses to the work the party's predecessor in interest had or could -give under the previous paragraph, plus a right to possession of the -Corresponding Source of the work from the predecessor in interest, if -the predecessor has it or can get it with reasonable efforts. - - You may not impose any further restrictions on the exercise of the -rights granted or affirmed under this License. For example, you may -not impose a license fee, royalty, or other charge for exercise of -rights granted under this License, and you may not initiate litigation -(including a cross-claim or counterclaim in a lawsuit) alleging that -any patent claim is infringed by making, using, selling, offering for -sale, or importing the Program or any portion of it. - - 11. Patents. - - A "contributor" is a copyright holder who authorizes use under this -License of the Program or a work on which the Program is based. The -work thus licensed is called the contributor's "contributor version". - - A contributor's "essential patent claims" are all patent claims -owned or controlled by the contributor, whether already acquired or -hereafter acquired, that would be infringed by some manner, permitted -by this License, of making, using, or selling its contributor version, -but do not include claims that would be infringed only as a -consequence of further modification of the contributor version. For -purposes of this definition, "control" includes the right to grant -patent sublicenses in a manner consistent with the requirements of -this License. - - Each contributor grants you a non-exclusive, worldwide, royalty-free -patent license under the contributor's essential patent claims, to -make, use, sell, offer for sale, import and otherwise run, modify and -propagate the contents of its contributor version. - - In the following three paragraphs, a "patent license" is any express -agreement or commitment, however denominated, not to enforce a patent -(such as an express permission to practice a patent or covenant not to -sue for patent infringement). To "grant" such a patent license to a -party means to make such an agreement or commitment not to enforce a -patent against the party. - - If you convey a covered work, knowingly relying on a patent license, -and the Corresponding Source of the work is not available for anyone -to copy, free of charge and under the terms of this License, through a -publicly available network server or other readily accessible means, -then you must either (1) cause the Corresponding Source to be so -available, or (2) arrange to deprive yourself of the benefit of the -patent license for this particular work, or (3) arrange, in a manner -consistent with the requirements of this License, to extend the patent -license to downstream recipients. "Knowingly relying" means you have -actual knowledge that, but for the patent license, your conveying the -covered work in a country, or your recipient's use of the covered work -in a country, would infringe one or more identifiable patents in that -country that you have reason to believe are valid. - - If, pursuant to or in connection with a single transaction or -arrangement, you convey, or propagate by procuring conveyance of, a -covered work, and grant a patent license to some of the parties -receiving the covered work authorizing them to use, propagate, modify -or convey a specific copy of the covered work, then the patent license -you grant is automatically extended to all recipients of the covered -work and works based on it. - - A patent license is "discriminatory" if it does not include within -the scope of its coverage, prohibits the exercise of, or is -conditioned on the non-exercise of one or more of the rights that are -specifically granted under this License. You may not convey a covered -work if you are a party to an arrangement with a third party that is -in the business of distributing software, under which you make payment -to the third party based on the extent of your activity of conveying -the work, and under which the third party grants, to any of the -parties who would receive the covered work from you, a discriminatory -patent license (a) in connection with copies of the covered work -conveyed by you (or copies made from those copies), or (b) primarily -for and in connection with specific products or compilations that -contain the covered work, unless you entered into that arrangement, -or that patent license was granted, prior to 28 March 2007. - - Nothing in this License shall be construed as excluding or limiting -any implied license or other defenses to infringement that may -otherwise be available to you under applicable patent law. - - 12. No Surrender of Others' Freedom. - - If conditions are imposed on you (whether by court order, agreement or -otherwise) that contradict the conditions of this License, they do not -excuse you from the conditions of this License. If you cannot convey a -covered work so as to satisfy simultaneously your obligations under this -License and any other pertinent obligations, then as a consequence you may -not convey it at all. For example, if you agree to terms that obligate you -to collect a royalty for further conveying from those to whom you convey -the Program, the only way you could satisfy both those terms and this -License would be to refrain entirely from conveying the Program. - - 13. Remote Network Interaction; Use with the GNU General Public License. - - Notwithstanding any other provision of this License, if you modify the -Program, your modified version must prominently offer all users -interacting with it remotely through a computer network (if your version -supports such interaction) an opportunity to receive the Corresponding -Source of your version by providing access to the Corresponding Source -from a network server at no charge, through some standard or customary -means of facilitating copying of software. This Corresponding Source -shall include the Corresponding Source for any work covered by version 3 -of the GNU General Public License that is incorporated pursuant to the -following paragraph. - - Notwithstanding any other provision of this License, you have -permission to link or combine any covered work with a work licensed -under version 3 of the GNU General Public License into a single -combined work, and to convey the resulting work. The terms of this -License will continue to apply to the part which is the covered work, -but the work with which it is combined will remain governed by version -3 of the GNU General Public License. - - 14. Revised Versions of this License. - - The Free Software Foundation may publish revised and/or new versions of -the GNU Affero General Public License from time to time. Such new versions -will be similar in spirit to the present version, but may differ in detail to -address new problems or concerns. - - Each version is given a distinguishing version number. If the -Program specifies that a certain numbered version of the GNU Affero General -Public License "or any later version" applies to it, you have the -option of following the terms and conditions either of that numbered -version or of any later version published by the Free Software -Foundation. If the Program does not specify a version number of the -GNU Affero General Public License, you may choose any version ever published -by the Free Software Foundation. - - If the Program specifies that a proxy can decide which future -versions of the GNU Affero General Public License can be used, that proxy's -public statement of acceptance of a version permanently authorizes you -to choose that version for the Program. - - Later license versions may give you additional or different -permissions. However, no additional obligations are imposed on any -author or copyright holder as a result of your choosing to follow a -later version. - - 15. Disclaimer of Warranty. - - THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY -APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT -HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY -OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, -THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR -PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM -IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF -ALL NECESSARY SERVICING, REPAIR OR CORRECTION. - - 16. Limitation of Liability. - - IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING -WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS -THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY -GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE -USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF -DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD -PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), -EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF -SUCH DAMAGES. - - 17. Interpretation of Sections 15 and 16. - - If the disclaimer of warranty and limitation of liability provided -above cannot be given local legal effect according to their terms, -reviewing courts shall apply local law that most closely approximates -an absolute waiver of all civil liability in connection with the -Program, unless a warranty or assumption of liability accompanies a -copy of the Program in return for a fee. - - END OF TERMS AND CONDITIONS - - How to Apply These Terms to Your New Programs - - If you develop a new program, and you want it to be of the greatest -possible use to the public, the best way to achieve this is to make it -free software which everyone can redistribute and change under these terms. - - To do so, attach the following notices to the program. It is safest -to attach them to the start of each source file to most effectively -state the exclusion of warranty; and each file should have at least -the "copyright" line and a pointer to where the full notice is found. - - - Copyright (C) - - This program is free software: you can redistribute it and/or modify - it under the terms of the GNU Affero General Public License as published - by the Free Software Foundation, either version 3 of the License, or - (at your option) any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU Affero General Public License for more details. - - You should have received a copy of the GNU Affero General Public License - along with this program. If not, see . - -Also add information on how to contact you by electronic and paper mail. - - If your software can interact with users remotely through a computer -network, you should also make sure that it provides a way for users to -get its source. For example, if your program is a web application, its -interface could display a "Source" link that leads users to an archive -of the code. There are many ways you could offer source, and different -solutions will be better for different programs; see section 13 for the -specific requirements. - - You should also get your employer (if you work as a programmer) or school, -if any, to sign a "copyright disclaimer" for the program, if necessary. -For more information on this, and how to apply and follow the GNU AGPL, see -. diff --git a/Makefile b/Makefile index 8674ca0..38593be 100644 --- a/Makefile +++ b/Makefile @@ -2,9 +2,13 @@ SRC_DIR := src SOURCES != find '$(SRC_DIR)' -iname '*.v' -V_RELEASE := weekly.2022.05 -V_PATH ?= v-$(V_RELEASE)/v -V := $(V_PATH) -showcc +LARCHIVE_VER := 3.5.2 +LARCHIVE_DIR := libarchive-$(LARCHIVE_VER) +LARCHIVE_LIB := $(LARCHIVE_DIR)/libarchive/libarchive.so + +# Custom V command for linking libarchive +# V := LDFLAGS=$(PWD)/$(LARCHIVE_LIB) v -cflags '-I$(PWD)/$(LARCHIVE_DIR) -I $(PWD)/$(LARCHIVE_DIR)' +V := v all: vieter @@ -25,7 +29,6 @@ prod: pvieter pvieter: $(SOURCES) $(V) -o pvieter -prod $(SRC_DIR) -# Only generate C code .PHONY: c c: $(V) -o vieter.c $(SRC_DIR) @@ -35,16 +38,16 @@ c: # Run the server in the default 'data' directory .PHONY: run run: vieter - API_KEY=test DOWNLOAD_DIR=data/downloads REPO_DIR=data/repo PKG_DIR=data/pkgs LOG_LEVEL=DEBUG ./vieter + API_KEY=test REPO_DIR=data LOG_LEVEL=DEBUG ./vieter .PHONY: run-prod run-prod: prod - API_KEY=test DOWNLOAD_DIR=data/downloads REPO_DIR=data/repo PKG_DIR=data/pkgs LOG_LEVEL=DEBUG ./pvieter + API_KEY=test REPO_DIR=data LOG_LEVEL=DEBUG ./vieter-prod # Same as run, but restart when the source code changes .PHONY: watch watch: - API_KEY=test DOWNLOAD_DIR=data/downloads REPO_DIR=data/repo PKG_DIR=data/pkgs LOG_LEVEL=DEBUG $(V) watch run vieter + API_KEY=test REPO_DIR=data LOG_LEVEL=DEBUG $(V) watch run vieter # =====OTHER===== @@ -57,17 +60,24 @@ lint: fmt: $(V) fmt -w $(SRC_DIR) -.PHONY: vet -vet: - $(V) vet -W $(SRC_DIR) +# Pulls & builds my personal build of the v compiler, required for this project to function +.PHONY: customv +customv: + rm -rf v-jjr + git clone \ + -b vweb-streaming \ + --single-branch \ + https://github.com/ChewingBever/v jjr-v + '$(MAKE)' -C jjr-v -# Build & patch the V compiler -.PHONY: v -v: v-$(V_RELEASE)/v -v-$(V_RELEASE)/v: - curl -Lo - 'https://github.com/vlang/v/archive/refs/tags/$(V_RELEASE).tar.gz' | tar xzf - - cd patches && sh patch.sh '../v-$(V_RELEASE)' - make -C 'v-$(V_RELEASE)' + +# =====LIBARCHIVE===== +.PHONY: libarchive +libarchive: $(LARCHIVE_LIB) +$(LARCHIVE_LIB): + curl -o - "https://libarchive.org/downloads/libarchive-${LARCHIVE_VER}.tar.gz" | tar xzf - + cd "libarchive-${LARCHIVE_VER}" && cmake . + '$(MAKE)' -C "libarchive-${LARCHIVE_VER}" clean: - rm -rf 'data' 'vieter' 'dvieter' 'pvieter' 'vieter.c' 'v-$(V_RELEASE)' + rm -rf '$(LARCHIVE_DIR)' 'data' 'vieter' 'dvieter' 'pvieter' 'vieter.c' diff --git a/README.md b/README.md index fbfa259..e3c79a2 100644 --- a/README.md +++ b/README.md @@ -16,11 +16,9 @@ that. ### Custom Compiler Currently, this program only works with a very slightly modified version of the -V standard library, and therefore the compiler. The changes that are made to -the standard V release can be found in the [patches](/patches) directory. You -can obtain this modified version of the compiler by running `make v`, which -will download, patch & build the compiler. Afterwards, all make commands that -require the V compiler will use this new binary. +V standard library, and therefore the compiler. The code for this can be found +[here](https://github.com/ChewingBever/v). For CI purposes & ease of use, you +can also clone & build that repo locally by running `make customv`. ## Features @@ -30,17 +28,3 @@ daemon to start builds, which are then uploaded to the server's repository. The server also allows for non-agents to upload packages, as long as they have the required secrets. This allows me to also develop non-git packages, such as my terminal, & upload them to the servers using CI. - -## Directory Structure - -The data directory consists of three main directories: - -* `downloads` - This is where packages are initially downloaded. Because vieter - moves files from this folder to the `pkgs` folder, these two folders should - best be on the same drive -* `pkgs` - This is where approved package files are stored. -* `repos` - Each repository gets a subfolder here. The subfolder contains the - uncompressed contents of the db file. - * Each repo subdirectory contains the compressed db & files archive for the - repository, alongside a directory called `files` which contains the - uncompressed contents. diff --git a/patches/parse_request_no_body.v b/patches/parse_request_no_body.v deleted file mode 100644 index c00a51c..0000000 --- a/patches/parse_request_no_body.v +++ /dev/null @@ -1,23 +0,0 @@ -// Parse the header of a raw HTTP request into a Request object -pub fn parse_request_head(mut reader io.BufferedReader) ?Request { - // request line - mut line := reader.read_line() ? - method, target, version := parse_request_line(line) ? - - // headers - mut header := new_header() - line = reader.read_line() ? - for line != '' { - key, value := parse_header(line) ? - header.add_custom(key, value) ? - line = reader.read_line() ? - } - header.coerce(canonicalize: true) - - return Request{ - method: method - url: target.str() - header: header - version: version - } -} diff --git a/patches/patch.sh b/patches/patch.sh deleted file mode 100755 index 9813976..0000000 --- a/patches/patch.sh +++ /dev/null @@ -1,13 +0,0 @@ -#!/usr/bin/env sh -# This file patches the downloaded V version -# Should be run from within the directory it's in, as it uses relative paths to the files used for patching. -# $1 is the path to the downloaded V version - -# Add parse_request_no_body -cat parse_request_no_body.v >> "$1"/vlib/net/http/request.v - -# Make sha256 functions public -sed -i \ - -e 's/\(fn (mut d Digest) checksum(\)/pub \1/' \ - -e 's/\(fn (mut d Digest) write(\)/pub \1/' \ - "$1"/vlib/crypto/sha256/sha256.v diff --git a/src/archive.v b/src/archive.v deleted file mode 100644 index 8d1314f..0000000 --- a/src/archive.v +++ /dev/null @@ -1,106 +0,0 @@ -// Bindings for the libarchive library - -#flag -larchive - -#include "archive.h" - -struct C.archive {} - -// Create a new archive struct for reading -fn C.archive_read_new() &C.archive - -// Configure the archive to work with zstd compression -fn C.archive_read_support_filter_zstd(&C.archive) - -// Configure the archive to work with gzip compression -fn C.archive_read_support_filter_gzip(&C.archive) - -// Configure the archive to work with a tarball content -fn C.archive_read_support_format_tar(&C.archive) - -// Open an archive for reading -fn C.archive_read_open_filename(&C.archive, &char, int) int - -// Go to next entry header in archive -fn C.archive_read_next_header(&C.archive, &&C.archive_entry) int - -// Skip reading the current entry -fn C.archive_read_data_skip(&C.archive) - -// Free an archive -fn C.archive_read_free(&C.archive) int - -// Read an archive entry's contents into a pointer -fn C.archive_read_data(&C.archive, voidptr, int) - -// Create a new archive struct for writing -fn C.archive_write_new() &C.archive - -// Sets the filter for the archive to gzip -fn C.archive_write_add_filter_gzip(&C.archive) - -// Sets to archive to "pax restricted" mode. Libarchive's "pax restricted" -// format is a tar format that uses pax extensions only when absolutely -// necessary. Most of the time, it will write plain ustar entries. This is the -// recommended tar format for most uses. You should explicitly use ustar format -// only when you have to create archives that will be readable on older -// systems; you should explicitly request pax format only when you need to -// preserve as many attributes as possible. -fn C.archive_write_set_format_pax_restricted(&C.archive) - -// Opens up the filename for writing -fn C.archive_write_open_filename(&C.archive, &char) - -// Write an entry to the archive file -fn C.archive_write_header(&C.archive, &C.archive_entry) - -// Write the data in the buffer to the archive -fn C.archive_write_data(&C.archive, voidptr, int) - -// Close an archive for writing -fn C.archive_write_close(&C.archive) - -// Free the write archive -fn C.archive_write_free(&C.archive) - -// Returns the name of the filter -fn C.archive_filter_code(&C.archive, int) int - -#include "archive_entry.h" - -struct C.archive_entry {} - -// Create a new archive_entry struct -fn C.archive_entry_new() &C.archive_entry - -// Get the filename of the given entry -fn C.archive_entry_pathname(&C.archive_entry) &char - -// Get an entry's file size -// Note: this function actually returns an i64, but as this can't be used as an -// arugment to malloc, we'll just roll with it & assume an entry is never -// bigger than 4 gigs -fn C.archive_entry_size(&C.archive_entry) int - -// Set the pathname for the entry -fn C.archive_entry_set_pathname(&C.archive_entry, &char) - -// Sets the file size of the entry -fn C.archive_entry_set_size(&C.archive_entry, i64) - -// Sets the file type for an entry -fn C.archive_entry_set_filetype(&C.archive_entry, u32) - -// Sets the file permissions for an entry -fn C.archive_entry_set_perm(&C.archive_entry, int) - -// Clears out an entry struct -fn C.archive_entry_clear(&C.archive_entry) - -// Copy over a stat struct to the archive entry -fn C.archive_entry_copy_stat(entry &C.archive_entry, const_stat &C.stat) - -#include - -// Compare two C strings; 0 means they're equal -fn C.strcmp(&char, &char) int diff --git a/src/archive/archive.v b/src/archive/archive.v new file mode 100644 index 0000000..ca911c7 --- /dev/null +++ b/src/archive/archive.v @@ -0,0 +1,43 @@ +module archive + +import os + +pub fn get_pkg_info(pkg_path string) ?string { + if !os.is_file(pkg_path) { + return error("'$pkg_path' doesn't exist or isn't a file.") + } + + a := C.archive_read_new() + entry := C.archive_entry_new() + mut r := 0 + + C.archive_read_support_filter_all(a) + C.archive_read_support_format_all(a) + + // TODO find out where does this 10240 come from + r = C.archive_read_open_filename(a, &char(pkg_path.str), 10240) + defer { + C.archive_read_free(a) + } + + if r != C.ARCHIVE_OK { + return error('Failed to open package.') + } + + // We iterate over every header in search of the .PKGINFO one + mut buf := voidptr(0) + for C.archive_read_next_header(a, &entry) == C.ARCHIVE_OK { + if C.strcmp(C.archive_entry_pathname(entry), c'.PKGINFO') == 0 { + size := C.archive_entry_size(entry) + + // TODO can this unsafe block be avoided? + buf = unsafe { malloc(size) } + C.archive_read_data(a, voidptr(buf), size) + break + } else { + C.archive_read_data_skip(a) + } + } + + return unsafe { cstring_to_vstring(&char(buf)) } +} diff --git a/src/archive/bindings.v b/src/archive/bindings.v new file mode 100644 index 0000000..678d715 --- /dev/null +++ b/src/archive/bindings.v @@ -0,0 +1,46 @@ +module archive + +#flag -larchive + +#include "archive.h" + +struct C.archive {} + +// Create a new archive struct +fn C.archive_read_new() &C.archive +fn C.archive_read_support_filter_all(&C.archive) +fn C.archive_read_support_format_all(&C.archive) + +// Open an archive for reading +fn C.archive_read_open_filename(&C.archive, &char, int) int + +// Go to next entry header in archive +fn C.archive_read_next_header(&C.archive, &&C.archive_entry) int + +// Skip reading the current entry +fn C.archive_read_data_skip(&C.archive) + +// Free an archive +fn C.archive_read_free(&C.archive) int + +// Read an archive entry's contents into a pointer +fn C.archive_read_data(&C.archive, voidptr, int) + +#include "archive_entry.h" + +struct C.archive_entry {} + +// Create a new archive_entry struct +fn C.archive_entry_new() &C.archive_entry + +// Get the filename of the given entry +fn C.archive_entry_pathname(&C.archive_entry) &char + +// Get an entry's file size +// Note: this function actually returns an i64, but as this can't be used as an arugment to malloc, we'll just roll with it & assume an entry is never bigger than 4 gigs +fn C.archive_entry_size(&C.archive_entry) int + +#include + +// Compare two C strings; 0 means they're equal +fn C.strcmp(&char, &char) int diff --git a/src/main.v b/src/main.v index c3c5ed9..af6b06c 100644 --- a/src/main.v +++ b/src/main.v @@ -5,6 +5,7 @@ import os import log import io import repo +import archive const port = 8000 @@ -16,7 +17,6 @@ struct App { web.Context pub: api_key string [required; web_global] - dl_dir string [required; web_global] pub mut: repo repo.Repo [required; web_global] } @@ -28,6 +28,7 @@ fn exit_with_message(code int, msg string) { } fn reader_to_file(mut reader io.BufferedReader, length int, path string) ? { + // Open up a file for writing to mut file := os.create(path) ? defer { file.close() @@ -79,24 +80,33 @@ fn main() { repo_dir := os.getenv_opt('REPO_DIR') or { exit_with_message(1, 'No repo directory was configured.') } - pkg_dir := os.getenv_opt('PKG_DIR') or { - exit_with_message(1, 'No package directory was configured.') - } - dl_dir := os.getenv_opt('DOWNLOAD_DIR') or { - exit_with_message(1, 'No download directory was configured.') + + repo := repo.Repo{ + dir: repo_dir + name: db_name } - // This also creates the directories if needed - repo := repo.new(repo_dir, pkg_dir) or { - exit_with_message(1, 'Failed to create required directories.') - } + // We create the upload directory during startup + if !os.is_dir(repo.pkg_dir()) { + os.mkdir_all(repo.pkg_dir()) or { + exit_with_message(2, "Failed to create repo directory '$repo.pkg_dir()'.") + } - os.mkdir_all(dl_dir) or { exit_with_message(1, 'Failed to create download directory.') } + logger.info("Created package directory '$repo.pkg_dir()'.") + } web.run(&App{ logger: logger api_key: key - dl_dir: dl_dir repo: repo }, port) } + +// fn main() { +// // archive.list_filenames() +// info := archive.get_pkg_info('test/jjr-joplin-desktop-2.6.10-4-x86_64.pkg.tar.zst') or { +// eprintln(err.msg) +// return +// } +// println(info) +// } diff --git a/src/package.v b/src/package.v deleted file mode 100644 index 03103c9..0000000 --- a/src/package.v +++ /dev/null @@ -1,271 +0,0 @@ -module package - -import os -import util - -// Represents a read archive -struct Pkg { -pub: - path string [required] - info PkgInfo [required] - files []string [required] - compression int [required] -} - -// Represents the contents of a .PKGINFO file -struct PkgInfo { -pub mut: - // Single values - name string - base string - version string - description string - size i64 - csize i64 - url string - arch string - build_date i64 - packager string - // md5sum string - // sha256sum string - pgpsig string - pgpsigsize i64 - // Array values - groups []string - licenses []string - replaces []string - depends []string - conflicts []string - provides []string - optdepends []string - makedepends []string - checkdepends []string -} - -// checksum calculates the md5 & sha256 hash of the package -pub fn (p &Pkg) checksum() ?(string, string) { - return util.hash_file(p.path) -} - -// parse_pkg_info_string parses a PkgInfo object from a string -fn parse_pkg_info_string(pkg_info_str &string) ?PkgInfo { - mut pkg_info := PkgInfo{} - - // Iterate over the entire string - for line in pkg_info_str.split_into_lines() { - // Skip any comment lines - if line.starts_with('#') { - continue - } - parts := line.split_nth('=', 2) - - if parts.len < 2 { - return error('Invalid line detected.') - } - - value := parts[1].trim_space() - key := parts[0].trim_space() - - match key { - // Single values - 'pkgname' { pkg_info.name = value } - 'pkgbase' { pkg_info.base = value } - 'pkgver' { pkg_info.version = value } - 'pkgdesc' { pkg_info.description = value } - 'csize' { continue } - 'size' { pkg_info.size = value.int() } - 'url' { pkg_info.url = value } - 'arch' { pkg_info.arch = value } - 'builddate' { pkg_info.build_date = value.int() } - 'packager' { pkg_info.packager = value } - 'md5sum' { continue } - 'sha256sum' { continue } - 'pgpsig' { pkg_info.pgpsig = value } - 'pgpsigsize' { pkg_info.pgpsigsize = value.int() } - // Array values - 'group' { pkg_info.groups << value } - 'license' { pkg_info.licenses << value } - 'replaces' { pkg_info.replaces << value } - 'depend' { pkg_info.depends << value } - 'conflict' { pkg_info.conflicts << value } - 'provides' { pkg_info.provides << value } - 'optdepend' { pkg_info.optdepends << value } - 'makedepend' { pkg_info.makedepends << value } - 'checkdepend' { pkg_info.checkdepends << value } - else { return error("Invalid key '$key'.") } - } - } - - return pkg_info -} - -// read_pkg extracts the file list & .PKGINFO contents from an archive -// NOTE: this command currently only supports zstd-compressed tarballs -pub fn read_pkg(pkg_path string) ?Pkg { - if !os.is_file(pkg_path) { - return error("'$pkg_path' doesn't exist or isn't a file.") - } - - a := C.archive_read_new() - entry := C.archive_entry_new() - - // Sinds 2020, all newly built Arch packages use zstd - C.archive_read_support_filter_zstd(a) - C.archive_read_support_filter_gzip(a) - // The content should always be a tarball - C.archive_read_support_format_tar(a) - - // TODO find out where does this 10240 come from - r := C.archive_read_open_filename(a, &char(pkg_path.str), 10240) - - if r != C.ARCHIVE_OK { - return error('Failed to open package.') - } - - defer { - C.archive_read_free(a) - } - - // 0: no compression (just a tarball) - // 1: gzip - // 14: zstd - compression_code := C.archive_filter_code(a, 0) - - mut files := []string{} - mut pkg_info := PkgInfo{} - - for C.archive_read_next_header(a, &entry) == C.ARCHIVE_OK { - pathname := C.archive_entry_pathname(entry) - - ignored_names := [c'.BUILDINFO', c'.INSTALL', c'.MTREE', c'.PKGINFO', c'.CHANGELOG'] - if ignored_names.all(C.strcmp(it, pathname) != 0) { - unsafe { - files << cstring_to_vstring(pathname) - } - } - - if C.strcmp(pathname, c'.PKGINFO') == 0 { - size := C.archive_entry_size(entry) - - // TODO can this unsafe block be avoided? - buf := unsafe { malloc(size) } - defer { - unsafe { - free(buf) - } - } - C.archive_read_data(a, buf, size) - - pkg_text := unsafe { buf.vstring_with_len(size).clone() } - - pkg_info = parse_pkg_info_string(pkg_text) ? - } else { - C.archive_read_data_skip(a) - } - } - - pkg_info.csize = i64(os.file_size(pkg_path)) - - return Pkg{ - path: pkg_path - info: pkg_info - files: files - compression: compression_code - } -} - -fn format_entry(key string, value string) string { - return '\n%$key%\n$value\n' -} - -// filename returns the correct filename of the package file -pub fn (pkg &Pkg) filename() string { - p := pkg.info - - ext := match pkg.compression { - 0 { '.tar' } - 1 { '.tar.gz' } - 14 { '.tar.zst' } - else { panic("Another compression code shouldn't be possible. Faulty code: $pkg.compression") } - } - - return '$p.name-$p.version-${p.arch}.pkg$ext' -} - -// to_desc returns a desc file valid string representation -// TODO calculate md5 & sha256 instead of believing the file -pub fn (pkg &Pkg) to_desc() string { - p := pkg.info - - // filename - mut desc := '%FILENAME%\n$pkg.filename()\n' - - desc += format_entry('NAME', p.name) - desc += format_entry('BASE', p.base) - desc += format_entry('VERSION', p.version) - - if p.description.len > 0 { - desc += format_entry('DESC', p.description) - } - - if p.groups.len > 0 { - desc += format_entry('GROUPS', p.groups.join_lines()) - } - - desc += format_entry('CSIZE', p.csize.str()) - desc += format_entry('ISIZE', p.size.str()) - - md5sum, sha256sum := pkg.checksum() or { '', '' } - - desc += format_entry('MD5SUM', md5sum) - desc += format_entry('SHA256SUM', sha256sum) - - // TODO add pgpsig stuff - - if p.url.len > 0 { - desc += format_entry('URL', p.url) - } - - if p.licenses.len > 0 { - desc += format_entry('LICENSE', p.licenses.join_lines()) - } - - desc += format_entry('ARCH', p.arch) - desc += format_entry('BUILDDATE', p.build_date.str()) - desc += format_entry('PACKAGER', p.packager) - - if p.replaces.len > 0 { - desc += format_entry('REPLACES', p.replaces.join_lines()) - } - - if p.conflicts.len > 0 { - desc += format_entry('CONFLICTS', p.conflicts.join_lines()) - } - - if p.provides.len > 0 { - desc += format_entry('PROVIDES', p.provides.join_lines()) - } - - if p.depends.len > 0 { - desc += format_entry('DEPENDS', p.depends.join_lines()) - } - - if p.optdepends.len > 0 { - desc += format_entry('OPTDEPENDS', p.optdepends.join_lines()) - } - - if p.makedepends.len > 0 { - desc += format_entry('MAKEDEPENDS', p.makedepends.join_lines()) - } - - if p.checkdepends.len > 0 { - desc += format_entry('CHECKDEPENDS', p.checkdepends.join_lines()) - } - - return '$desc\n' -} - -// to_files returns a files file valid string representation -pub fn (pkg &Pkg) to_files() string { - return '%FILES%\n$pkg.files.join_lines()\n' -} diff --git a/src/repo.v b/src/repo.v new file mode 100644 index 0000000..6b307e8 --- /dev/null +++ b/src/repo.v @@ -0,0 +1,52 @@ +module repo + +import os + +const pkgs_subpath = 'pkgs' + +// Dummy struct to work around the fact that you can only share structs, maps & +// arrays +pub struct Dummy { + x int +} + +// Handles management of a repository. Package files are stored in '$dir/pkgs' +// & moved there if necessary. +pub struct Repo { +mut: + mutex shared Dummy +pub: + dir string [required] + name string [required] +} + +pub fn (r &Repo) pkg_dir() string { + return os.join_path_single(r.dir, repo.pkgs_subpath) +} + +// Returns path to the given package, prepended with the repo's path. +pub fn (r &Repo) pkg_path(pkg string) string { + return os.join_path(r.dir, repo.pkgs_subpath, pkg) +} + +pub fn (r &Repo) exists(pkg string) bool { + return os.exists(r.pkg_path(pkg)) +} + +// Returns the full path to the database file +pub fn (r &Repo) db_path() string { + return os.join_path_single(r.dir, '${r.name}.tar.gz') +} + +pub fn (r &Repo) add_package(pkg_path string) ? { + mut res := os.Result{} + + lock r.mutex { + res = os.execute("repo-add '$r.db_path()' '$pkg_path'") + } + + if res.exit_code != 0 { + println(res.output) + return error('repo-add failed.') + } +} diff --git a/src/repo/repo.v b/src/repo/repo.v deleted file mode 100644 index ded30ba..0000000 --- a/src/repo/repo.v +++ /dev/null @@ -1,95 +0,0 @@ -module repo - -import os -import package - -// subpath where the uncompressed version of the files archive is stored -const files_subpath = 'files' - -// subpath where the uncompressed version of the repo archive is stored -const repo_subpath = 'repo' - -// Dummy struct to work around the fact that you can only share structs, maps & -// arrays -pub struct Dummy { - x int -} - -// This struct manages a single repository. -pub struct Repo { -mut: - mutex shared Dummy -pub: - // Where to store repository files; should exist - repo_dir string [required] - // Where to find packages; packages are expected to all be in the same directory - pkg_dir string [required] -} - -// new creates a new Repo & creates the directories as needed -pub fn new(repo_dir string, pkg_dir string) ?Repo { - if !os.is_dir(repo_dir) { - os.mkdir_all(repo_dir) or { return error('Failed to create repo directory.') } - } - - if !os.is_dir(pkg_dir) { - os.mkdir_all(pkg_dir) or { return error('Failed to create package directory.') } - } - - return Repo{ - repo_dir: repo_dir - pkg_dir: pkg_dir - } -} - -// add_from_path adds a package from an arbitrary path & moves it into the pkgs -// directory if necessary. -pub fn (r &Repo) add_from_path(pkg_path string) ?bool { - pkg := package.read_pkg(pkg_path) or { return error('Failed to read package file: $err.msg') } - - added := r.add(pkg) ? - - // If the add was successful, we move the file to the packages directory - if added { - dest_path := os.real_path(os.join_path_single(r.pkg_dir, pkg.filename())) - - // Only move the file if it's not already in the package directory - if dest_path != os.real_path(pkg_path) { - os.mv(pkg_path, dest_path) ? - } - } - - return added -} - -// add adds a given Pkg to the repository -fn (r &Repo) add(pkg &package.Pkg) ?bool { - pkg_dir := r.pkg_path(pkg) - - // We can't add the same package twice - if os.exists(pkg_dir) { - return false - } - - os.mkdir(pkg_dir) or { return error('Failed to create package directory.') } - - os.write_file(os.join_path_single(pkg_dir, 'desc'), pkg.to_desc()) or { - os.rmdir_all(pkg_dir) ? - - return error('Failed to write desc file.') - } - os.write_file(os.join_path_single(pkg_dir, 'files'), pkg.to_files()) or { - os.rmdir_all(pkg_dir) ? - - return error('Failed to write files file.') - } - - r.sync() ? - - return true -} - -// Returns the path where the given package's desc & files files are stored -fn (r &Repo) pkg_path(pkg &package.Pkg) string { - return os.join_path(r.repo_dir, '$pkg.info.name-$pkg.info.version') -} diff --git a/src/repo/sync.v b/src/repo/sync.v deleted file mode 100644 index d6080e0..0000000 --- a/src/repo/sync.v +++ /dev/null @@ -1,79 +0,0 @@ -module repo - -import os - -fn archive_add_entry(archive &C.archive, entry &C.archive_entry, file_path &string, inner_path &string) { - st := C.stat{} - - unsafe { - C.stat(&char(file_path.str), &st) - } - - C.archive_entry_set_pathname(entry, &char(inner_path.str)) - C.archive_entry_copy_stat(entry, &st) - C.archive_write_header(archive, entry) - - mut fd := C.open(&char(file_path.str), C.O_RDONLY) - defer { - C.close(fd) - } - - // Write the file to the archive - buf := [8192]byte{} - mut len := C.read(fd, &buf, sizeof(buf)) - - for len > 0 { - C.archive_write_data(archive, &buf, len) - - len = C.read(fd, &buf, sizeof(buf)) - } -} - -// Re-generate the repo archive files -fn (r &Repo) sync() ? { - // TODO also write files archive - lock r.mutex { - a_db := C.archive_write_new() - a_files := C.archive_write_new() - - entry := C.archive_entry_new() - - // This makes the archive a gzip-compressed tarball - C.archive_write_add_filter_gzip(a_db) - C.archive_write_set_format_pax_restricted(a_db) - C.archive_write_add_filter_gzip(a_files) - C.archive_write_set_format_pax_restricted(a_files) - - db_path := os.join_path_single(r.repo_dir, 'vieter.db.tar.gz') - files_path := os.join_path_single(r.repo_dir, 'vieter.files.tar.gz') - - C.archive_write_open_filename(a_db, &char(db_path.str)) - C.archive_write_open_filename(a_files, &char(files_path.str)) - - // Iterate over each directory - for d in os.ls(r.repo_dir) ?.filter(os.is_dir(os.join_path_single(r.repo_dir, - it))) { - // desc - mut inner_path := os.join_path_single(d, 'desc') - mut actual_path := os.join_path_single(r.repo_dir, inner_path) - - archive_add_entry(a_db, entry, actual_path, inner_path) - archive_add_entry(a_files, entry, actual_path, inner_path) - - C.archive_entry_clear(entry) - - // files - inner_path = os.join_path_single(d, 'files') - actual_path = os.join_path_single(r.repo_dir, inner_path) - - archive_add_entry(a_files, entry, actual_path, inner_path) - - C.archive_entry_clear(entry) - } - - C.archive_write_close(a_db) - C.archive_write_free(a_db) - C.archive_write_close(a_files) - C.archive_write_free(a_files) - } -} diff --git a/src/routes.v b/src/routes.v index 45e7fa5..0b570ca 100644 --- a/src/routes.v +++ b/src/routes.v @@ -4,11 +4,9 @@ import web import os import repo import time -import rand const prefixes = ['B', 'KB', 'MB', 'GB'] -// pretty_bytes converts a byte count to human-readable version fn pretty_bytes(bytes int) string { mut i := 0 mut n := f32(bytes) @@ -25,83 +23,67 @@ fn is_pkg_name(s string) bool { return s.contains('.pkg') } -['/health'; get] -pub fn (mut app App) healthcheck() web.Result { - return app.text('Healthy') -} - -// get_root handles a GET request for a file on the root ['/:filename'; get] fn (mut app App) get_root(filename string) web.Result { mut full_path := '' - if filename.ends_with('.db') || filename.ends_with('.files') { - full_path = os.join_path_single(app.repo.repo_dir, '${filename}.tar.gz') - } else if filename.ends_with('.db.tar.gz') || filename.ends_with('.files.tar.gz') { - full_path = os.join_path_single(app.repo.repo_dir, '$filename') + if is_pkg_name(filename) { + full_path = os.join_path_single(app.repo.pkg_dir(), filename) } else { - full_path = os.join_path_single(app.repo.pkg_dir, filename) + full_path = os.join_path_single(app.repo.dir, filename) } return app.file(full_path) } -['/publish'; post] -fn (mut app App) put_package() web.Result { +['/pkgs/:pkg'; put] +fn (mut app App) put_package(pkg string) web.Result { if !app.is_authorized() { return app.text('Unauthorized.') } - mut pkg_path := '' + if !is_pkg_name(pkg) { + app.lwarn("Invalid package name '$pkg'.") + + return app.text('Invalid filename.') + } + + if app.repo.exists(pkg) { + app.lwarn("Duplicate package '$pkg'") + + return app.text('File already exists.') + } + + pkg_path := app.repo.pkg_path(pkg) if length := app.req.header.get(.content_length) { - // Generate a random filename for the temp file - pkg_path = os.join_path_single(app.dl_dir, rand.uuid_v4()) - - for os.exists(pkg_path) { - pkg_path = os.join_path_single(app.dl_dir, rand.uuid_v4()) - } - - app.ldebug("Uploading $length (${pretty_bytes(length.int())}) bytes to '$pkg_path'.") + app.ldebug("Uploading $length (${pretty_bytes(length.int())}) bytes to package '$pkg'.") // This is used to time how long it takes to upload a file mut sw := time.new_stopwatch(time.StopWatchOptions{ auto_start: true }) reader_to_file(mut app.reader, length.int(), pkg_path) or { - app.lwarn("Failed to upload '$pkg_path'") + app.lwarn("Failed to upload package '$pkg'") return app.text('Failed to upload file.') } sw.stop() - app.ldebug("Upload of '$pkg_path' completed in ${sw.elapsed().seconds():.3}s.") + app.ldebug("Upload of package '$pkg' completed in ${sw.elapsed().seconds():.3}s.") } else { - app.lwarn('Tried to upload package without specifying a Content-Length.') + app.lwarn("Tried to upload package '$pkg' without specifying a Content-Length.") return app.text("Content-Type header isn't set.") } - added := app.repo.add_from_path(pkg_path) or { - app.lerror('Error while adding package: $err.msg') + app.repo.add_package(pkg_path) or { + app.lwarn("Failed to add package '$pkg' to database.") - os.rm(pkg_path) or { app.lerror("Failed to remove download '$pkg_path'.") } + os.rm(pkg_path) or { println('Failed to remove $pkg_path') } - return app.text('Failed to add package.') - } - if !added { - os.rm(pkg_path) or { app.lerror("Failed to remove download '$pkg_path'.") } - - app.lwarn('Duplicate package.') - - return app.text('File already exists.') + return app.text('Failed to add package to repo.') } - app.linfo("Added '$pkg_path' to repository.") + app.linfo("Added '$pkg' to repository.") return app.text('Package added successfully.') } - -// add_package PUT a new package to the server -['/add'; put] -pub fn (mut app App) add_package() web.Result { - return app.text('') -} diff --git a/src/util.v b/src/util.v deleted file mode 100644 index f81a256..0000000 --- a/src/util.v +++ /dev/null @@ -1,34 +0,0 @@ -module util - -import os -import crypto.md5 -import crypto.sha256 - -// hash_file returns the md5 & sha256 hash of a given file -// TODO actually implement sha256 -pub fn hash_file(path &string) ?(string, string) { - file := os.open(path) or { return error('Failed to open file.') } - - mut md5sum := md5.new() - mut sha256sum := sha256.new() - - buf_size := int(1_000_000) - mut buf := []byte{len: buf_size} - mut bytes_left := os.file_size(path) - - for bytes_left > 0 { - // TODO check if just breaking here is safe - bytes_read := file.read(mut buf) or { return error('Failed to read from file.') } - bytes_left -= u64(bytes_read) - - // For now we'll assume that this always works - md5sum.write(buf[..bytes_read]) or { - return error('Failed to update md5 checksum. This should never happen.') - } - sha256sum.write(buf[..bytes_read]) or { - return error('Failed to update sha256 checksum. This should never happen.') - } - } - - return md5sum.checksum().hex(), sha256sum.checksum().hex() -} diff --git a/src/web/logging.v b/src/web/logging.v index fc697ff..66426f2 100644 --- a/src/web/logging.v +++ b/src/web/logging.v @@ -2,34 +2,28 @@ module web import log -// log reate a log message with the given level pub fn (mut ctx Context) log(msg &string, level log.Level) { lock ctx.logger { ctx.logger.send_output(msg, level) } } -// lfatal create a log message with the fatal level pub fn (mut ctx Context) lfatal(msg &string) { ctx.log(msg, log.Level.fatal) } -// lerror create a log message with the error level pub fn (mut ctx Context) lerror(msg &string) { ctx.log(msg, log.Level.error) } -// lwarn create a log message with the warn level pub fn (mut ctx Context) lwarn(msg &string) { ctx.log(msg, log.Level.warn) } -// linfo create a log message with the info level pub fn (mut ctx Context) linfo(msg &string) { ctx.log(msg, log.Level.info) } -// ldebug create a log message with the debug level pub fn (mut ctx Context) ldebug(msg &string) { ctx.log(msg, log.Level.debug) } diff --git a/src/web/web.v b/src/web/web.v index bbb909f..404bd08 100644 --- a/src/web/web.v +++ b/src/web/web.v @@ -187,14 +187,14 @@ struct Route { } // Defining this method is optional. -// init_server is called at server start. +// This method called at server start. // You can use it for initializing globals. pub fn (ctx Context) init_server() { eprintln('init_server() has been deprecated, please init your web app in `fn main()`') } // Defining this method is optional. -// before_request is called before every request (aka middleware). +// This method called before every request (aka middleware). // Probably you can use it for check user session cookie or add header. pub fn (ctx Context) before_request() {} @@ -206,7 +206,7 @@ pub struct Cookie { http_only bool } -// send_response_to_client sends a response to the client +// web intern function [manualfree] pub fn (mut ctx Context) send_response_to_client(mimetype string, res string) bool { if ctx.done { @@ -230,38 +230,39 @@ pub fn (mut ctx Context) send_response_to_client(mimetype string, res string) bo return true } -// html HTTP_OK with s as payload with content-type `text/html` +// Response HTTP_OK with s as payload with content-type `text/html` pub fn (mut ctx Context) html(s string) Result { ctx.send_response_to_client('text/html', s) return Result{} } -// text HTTP_OK with s as payload with content-type `text/plain` +// Response HTTP_OK with s as payload with content-type `text/plain` pub fn (mut ctx Context) text(s string) Result { ctx.send_response_to_client('text/plain', s) return Result{} } -// json HTTP_OK with json_s as payload with content-type `application/json` +// Response HTTP_OK with json_s as payload with content-type `application/json` pub fn (mut ctx Context) json(j T) Result { json_s := json.encode(j) ctx.send_response_to_client('application/json', json_s) return Result{} } -// json_pretty Response HTTP_OK with a pretty-printed JSON result +// Response HTTP_OK with a pretty-printed JSON result pub fn (mut ctx Context) json_pretty(j T) Result { json_s := json.encode_pretty(j) ctx.send_response_to_client('application/json', json_s) return Result{} } -// file Response HTTP_OK with file as payload +// Response HTTP_OK with file as payload // This function manually implements responses because it needs to stream the file contents pub fn (mut ctx Context) file(f_path string) Result { if ctx.done { return Result{} } + ctx.done = true if !os.is_file(f_path) { return ctx.not_found() @@ -328,13 +329,13 @@ pub fn (mut ctx Context) file(f_path string) Result { return Result{} } -// ok Response HTTP_OK with s as payload +// Response HTTP_OK with s as payload pub fn (mut ctx Context) ok(s string) Result { ctx.send_response_to_client(ctx.content_type, s) return Result{} } -// server_error Response a server error +// Response a server error pub fn (mut ctx Context) server_error(ecode int) Result { $if debug { eprintln('> ctx.server_error ecode: $ecode') @@ -346,7 +347,7 @@ pub fn (mut ctx Context) server_error(ecode int) Result { return Result{} } -// redirect Redirect to an url +// Redirect to an url pub fn (mut ctx Context) redirect(url string) Result { if ctx.done { return Result{} @@ -359,7 +360,7 @@ pub fn (mut ctx Context) redirect(url string) Result { return Result{} } -// not_found Send an not_found response +// Send an not_found response pub fn (mut ctx Context) not_found() Result { if ctx.done { return Result{} @@ -369,7 +370,7 @@ pub fn (mut ctx Context) not_found() Result { return Result{} } -// set_cookie Sets a cookie +// Sets a cookie pub fn (mut ctx Context) set_cookie(cookie Cookie) { mut cookie_data := []string{} mut secure := if cookie.secure { 'Secure;' } else { '' } @@ -382,17 +383,17 @@ pub fn (mut ctx Context) set_cookie(cookie Cookie) { ctx.add_header('Set-Cookie', '$cookie.name=$cookie.value; $data') } -// set_content_type Sets the response content type +// Sets the response content type pub fn (mut ctx Context) set_content_type(typ string) { ctx.content_type = typ } -// set_cookie_with_expire_date Sets a cookie with a `expire_data` +// Sets a cookie with a `expire_data` pub fn (mut ctx Context) set_cookie_with_expire_date(key string, val string, expire_date time.Time) { ctx.add_header('Set-Cookie', '$key=$val; Secure; HttpOnly; expires=$expire_date.utc_string()') } -// get_cookie Gets a cookie by a key +// Gets a cookie by a key pub fn (ctx &Context) get_cookie(key string) ?string { // TODO refactor mut cookie_header := ctx.get_header('cookie') if cookie_header == '' { @@ -412,7 +413,7 @@ pub fn (ctx &Context) get_cookie(key string) ?string { // TODO refactor return error('Cookie not found') } -// set_status Sets the response status +// Sets the response status pub fn (mut ctx Context) set_status(code int, desc string) { if code < 100 || code > 599 { ctx.status = '500 Internal Server Error' @@ -421,12 +422,12 @@ pub fn (mut ctx Context) set_status(code int, desc string) { } } -// add_header Adds an header to the response with key and val +// Adds an header to the response with key and val pub fn (mut ctx Context) add_header(key string, val string) { ctx.header.add_custom(key, val) or {} } -// get_header Returns the header data from the key +// Returns the header data from the key pub fn (ctx &Context) get_header(key string) string { return ctx.req.header.get_custom(key) or { '' } } @@ -435,7 +436,7 @@ interface DbInterface { db voidptr } -// run runs the app +// run_app [manualfree] pub fn run(global_app &T, port int) { mut l := net.listen_tcp(.ip6, ':$port') or { panic('failed to listen $err.code $err') } @@ -477,7 +478,6 @@ pub fn run(global_app &T, port int) { } } -// handle_conn handles a connection [manualfree] fn handle_conn(mut conn net.TcpConn, mut app T, routes map[string]Route) { conn.set_read_timeout(30 * time.second) @@ -615,7 +615,6 @@ fn handle_conn(mut conn net.TcpConn, mut app T, routes map[string]Route) { conn.write(web.http_404.bytes()) or {} } -// route_matches returns wether a route matches fn route_matches(url_words []string, route_words []string) ?[]string { // URL path should be at least as long as the route path // except for the catchall route (`/:path...`) @@ -658,7 +657,7 @@ fn route_matches(url_words []string, route_words []string) ?[]string { return params } -// serve_if_static checks if request is for a static file and serves it +// check if request is for a static file and serves it // returns true if we served a static file, false otherwise [manualfree] fn serve_if_static(mut app T, url urllib.URL) bool { @@ -677,7 +676,6 @@ fn serve_if_static(mut app T, url urllib.URL) bool { return true } -// scan_static_directory makes a static route for each file in a directory fn (mut ctx Context) scan_static_directory(directory_path string, mount_path string) { files := os.ls(directory_path) or { panic(err) } if files.len > 0 { @@ -697,7 +695,7 @@ fn (mut ctx Context) scan_static_directory(directory_path string, mount_path str } } -// handle_static Handles a directory static +// Handles a directory static // If `root` is set the mount path for the dir will be in '/' pub fn (mut ctx Context) handle_static(directory_path string, root bool) bool { if ctx.done || !os.exists(directory_path) { @@ -726,7 +724,7 @@ pub fn (mut ctx Context) mount_static_folder_at(directory_path string, mount_pat return true } -// serve_static Serves a file static +// Serves a file static // `url` is the access path on the site, `file_path` is the real path to the file, `mime_type` is the file type pub fn (mut ctx Context) serve_static(url string, file_path string) { ctx.static_files[url] = file_path @@ -735,7 +733,7 @@ pub fn (mut ctx Context) serve_static(url string, file_path string) { ctx.static_mime_types[url] = web.mime_types[ext] } -// ip Returns the ip address from the current user +// Returns the ip address from the current user pub fn (ctx &Context) ip() string { mut ip := ctx.req.header.get(.x_forwarded_for) or { '' } if ip == '' { @@ -751,23 +749,22 @@ pub fn (ctx &Context) ip() string { return ip } -// error Set s to the form error +// Set s to the form error pub fn (mut ctx Context) error(s string) { println('web error: $s') ctx.form_error = s } -// not_found Returns an empty result +// Returns an empty result pub fn not_found() Result { return Result{} } -// send_string fn send_string(mut conn net.TcpConn, s string) ? { conn.write(s.bytes()) ? } -// filter Do not delete. +// Do not delete. // It used by `vlib/v/gen/c/str_intp.v:130` for string interpolation inside web templates // TODO: move it to template render fn filter(s string) string { diff --git a/test.py b/test.py deleted file mode 100644 index 5721310..0000000 --- a/test.py +++ /dev/null @@ -1,145 +0,0 @@ -import random -import tempfile -import tarfile -from pathlib import Path -import uuid -import argparse -import asyncio -import aiohttp -import sys - - -# A list of words the program can choose from -WORDS = ["alpha", "bravo", "charlie", "delta", "echo", "foxtrot", "golf", - "hotel", "india", "juliet", "kilo", "lima", "mike", "november", - "oscar", "papa", "quebec", "romeo", "sierra", "tango", "uniform", - "victor", "whiskey", "xray", "yankee", "zulu"] -SEED = 2022 - - -def random_words(words, min_len, max_len=None): - """ - Returns a random list of words, with a length randomly choosen between - min_len and max_len. If max_len is None, it is equal to the length of - words. - """ - if max_len is None: - max_len = len(words) - - k = random.randint(min_len, max_len) - - return random.choices(words, k=k) - -def random_lists(words, n, min_len, max_len=None): - return [random_words(words, min_len, max_len) for _ in range(n)] - -def create_random_pkginfo(words, name_min_len, name_max_len): - """ - Generates a random .PKGINFO - """ - name = "-".join(random_words(words, name_min_len, name_max_len)) - ver = "0.1.0-1" # doesn't matter what it is anyways - - # TODO add random dependencies (all types) - - data = { - "pkgname": name, - "pkgbase": name, - "pkgver": ver, - "arch": "x86_64" - } - - return "\n".join(f"{key} = {value}" for key, value in data.items()) - -def create_random_package(tmpdir, words, pkg_name_min_len, pkg_name_max_len, min_files, max_files, min_filename_len, max_filename_len): - """ - Creates a random, but valid Arch package, using the provided tmpdir. Output - is the path to the created package tarball. - """ - - sub_path = tmpdir / uuid.uuid4().hex - sub_path.mkdir() - - tar_path = sub_path / "archive.pkg.tar.gz" - - def remove_prefix(tar_info): - tar_info.name = tar_info.name[len(str(sub_path)):] - - return tar_info - - with tarfile.open(tar_path, "w:gz") as tar: - # Add random .PKGINFO file - pkginfo_file = sub_path / ".PKGINFO" - pkginfo_file.write_text(create_random_pkginfo(words, pkg_name_min_len, pkg_name_max_len)) - tar.add(pkginfo_file, filter=remove_prefix) - - # Create random files - file_count = random.randint(min_files, max_files) - - for words in random_lists(words, file_count, min_filename_len, max_filename_len): - path = sub_path / 'usr' / ('/'.join(words) + ".txt") - path.parent.mkdir(parents=True, exist_ok=True) - path.write_text(' '.join(words)) - - tar.add(path, filter=remove_prefix) - - return tar_path - - -async def check_output(r): - good = {"File already exists.", "Package added successfully."} - txt = await r.text() - - return (txt in good, txt) - - -async def upload_random_package(tar_path, sem): - async with sem: - with open(tar_path, 'rb') as f: - async with aiohttp.ClientSession() as s: - async with s.post("http://localhost:8000/publish", data=f.read(), headers={"x-api-key": "test"}) as r: - return await check_output(r) - - -async def main(): - parser = argparse.ArgumentParser(description="Test vieter by uploading random package files.") - - parser.add_argument("count", help="How many packages to upload.", default=1, type=int) - parser.add_argument("-p", "--parallel", help="How many uploads to run in parallel.", default=1, type=int) - parser.add_argument("-s", "--seed", help="Seed for the randomizer.", default=SEED, type=int) - parser.add_argument("--min-files", help="Minimum amount of files to add to an archive.", default=5, type=int) - parser.add_argument("--max-files", help="Max amount of files to add to an archive.", default=10, type=int) - parser.add_argument("--min-filename-length", help="Minimum amount of words to use for generating filenames.", default=1, type=int) - parser.add_argument("--max-filename-length", help="Max amount of words to use for generating filenames.", default=5, type=int) - parser.add_argument("--min-pkg-name-length", help="Minimum amount of words to use for creating package name.", default=1, type=int) - parser.add_argument("--max-pkg-name-length", help="Max amount of words to use for creating package name.", default=3, type=int) - parser.add_argument("--words", help="Words to use for randomizing.", default=WORDS, type=lambda s: s.split(',')) - # parser.add_argument("--words", help="Words to use for randomizing.", default=WORDS, type=) - # parser.add_argument("-d", "--dir", help="Directory to create ") - - args = parser.parse_args() - - sem = asyncio.BoundedSemaphore(args.parallel) - random.seed(args.seed) - - - with tempfile.TemporaryDirectory() as tmpdirname: - tmpdir = Path(tmpdirname) - - # We generate the tars in advance because they're not async anyways - print("Generating tarballs...") - tars = { - create_random_package(tmpdir, args.words, args.min_pkg_name_length, args.max_pkg_name_length, args.min_files, args.max_files, args.min_filename_length, args.max_filename_length) - for _ in range(args.count) - } - - print("Sending requests...") - res = await asyncio.gather(*(upload_random_package(tar, sem) for tar in tars)) - - # Generate status report - if any(not x[0] for x in res): - sys.exit(1) - - -if __name__ == "__main__": - asyncio.run(main())