diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..be3f7b2 --- /dev/null +++ b/LICENSE @@ -0,0 +1,661 @@ + 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/README.md b/README.md index 2776cb6..9321c68 100644 --- a/README.md +++ b/README.md @@ -1,49 +1,75 @@ # SpendHound -SpendHound is a production-ready, full-stack AI expense tracker with multimodal receipt extraction, RAG-powered financial chat, multi-currency shared ledgers, recurring expense automation, budget analytics, and automated monthly PDF reports. Optimized for Italian receipts; works with any language. +SpendHound is a self-hosted, full-stack AI expense tracker with multimodal receipt parsing, +RAG-powered financial chat, multi-currency shared ledgers, recurring expense automation, +budget analytics, and automated monthly PDF reports. Optimized for Italian receipts; works +with any language. + +Licensed under the [GNU Affero General Public License v3.0](LICENSE). + +--- ## Features -- Google OAuth with admin approval flow -- Multimodal receipt upload — extracts merchant, amount, date, and line items via LLM; falls back through a 3-tier OCR stack (pdfplumber → pdfminer → pytesseract) for large files +- Google OAuth with admin approval gate +- Multimodal receipt upload: extracts merchant, amount, date, and line items via LLM; falls + back through a 3-tier OCR stack (pdfplumber -> pdfminer -> pytesseract) for large files - Bank statement PDF import with per-transaction confidence scores - Manual expense entry with full edit support -- RAG semantic item classification using pgvector + Ollama embeddings with a user-correction learning loop +- RAG semantic item classification using pgvector + Ollama embeddings with a user-correction + learning loop - Editable categories, merchant rules, and item keyword rules (global admin-wide + per-user) - SSE-streaming financial chat grounded on 90 days of live expense data - Multi-currency shared ledgers with role-based membership and full audit trail -- Recurring expenses (6 cadence types: monthly, quarterly, annual, custom-interval, prepaid, one-time) with auto-generation +- Recurring expenses (monthly, yearly, custom-interval, prepaid, one-time) with auto-generation - Monthly dashboard analytics and budget-versus-actual tracking - Review queue for low-confidence or uncategorized items - CSV and JSON exports - Automated monthly PDF report delivery via Puppeteer + Resend - Admin approval panel with JWT-signed email-link tokens -- Pluggable LLM providers: Ollama (default, local, no API key required), Anthropic Claude, OpenAI, Nebius, and OpenAI-compatible endpoints +- Pluggable LLM providers: Ollama (default, local, no API key required), Anthropic Claude, + OpenAI, DeepSeek, Nebius, and any OpenAI-compatible endpoint (OpenRouter, Groq, Together AI, + Mistral) +- Celery + Redis task queue: receipt extraction, statement parsing, and report delivery all + run as durable background tasks that survive process restarts +- Prometheus metrics + Grafana dashboards for request latency, LLM latency, queue depth, + and rate-limit hits +- Demo mode: public Bruce Wayne account, automatically reset every 30 minutes via Celery Beat +- GDPR Article 17 compliance: full account erasure and selective data deletion endpoints + +--- ## Stack | Layer | Technology | |---|---| -| Backend | FastAPI 0.115 · Python 3.12 · SQLAlchemy 2.0 async · asyncpg | +| Backend | FastAPI 0.115 + Python 3.12 + SQLAlchemy 2.0 async + asyncpg | +| Task queue | Celery 5 + Redis 7 | | Database | PostgreSQL 16 + pgvector extension | -| Migrations | Alembic | -| Frontend | Next.js 14.2 App Router · NextAuth.js 4.24 · TailwindCSS · Recharts | -| Auth | Google OAuth 2.0 · HS256 JWT (python-jose) | -| LLM | Anthropic Claude · OpenAI · Ollama · Nebius (pluggable factory) | -| PDF generation | Puppeteer (headless Chromium, via Next.js internal route) | +| Migrations | Alembic (21 migration files, full up/down coverage) | +| Frontend | Next.js 14.2 App Router + NextAuth.js 4.24 + TailwindCSS + Recharts | +| Auth | Google OAuth 2.0 + HS256 JWT (python-jose) | +| LLM | Anthropic Claude, OpenAI, Ollama, DeepSeek, Nebius, OpenRouter, Groq, Together AI, Mistral | +| Embeddings | Ollama (768-dim) + pgvector | +| PDF generation | Puppeteer (headless Chromium via Next.js internal route) | | Email | Resend API | +| Observability | Prometheus + Grafana (provisioned dashboards) + Flower (Celery monitor) | +| Secrets | Infisical (CLI-injected at runtime; no .env files in source) | | Containerization | Docker + Docker Compose | --- ## Prerequisites -| Tool | Minimum version | -|---|---| -| Docker + Compose v2 plugin | Docker 24+ | -| Python | 3.12+ (local dev only) | -| Node.js | 18+ (local dev only) | -| Ollama | any recent release (optional, for local LLM) | +| Tool | Minimum version | Notes | +|---|---|---| +| Docker + Compose v2 plugin | Docker 24+ | Required for Docker-based setup | +| Infisical CLI | any recent release | Required for the recommended dev/prod workflow | +| Python | 3.12+ | Local dev only (without Docker) | +| Node.js | 18+ | Local dev only (without Docker) | +| Ollama | any recent release | Optional -- local LLM; no API key needed | + +Install Infisical CLI: https://infisical.com/docs/cli/overview --- @@ -51,26 +77,37 @@ SpendHound is a production-ready, full-stack AI expense tracker with multimodal SpendHound requires a Google OAuth 2.0 client for sign-in. -1. Go to [console.cloud.google.com](https://console.cloud.google.com) → **APIs & Services** → **Credentials** +1. Go to [console.cloud.google.com](https://console.cloud.google.com) > **APIs & Services** > **Credentials** 2. Create an **OAuth 2.0 Client ID** (type: Web application) -3. Add these to **Authorised redirect URIs**: - - `http://localhost:3000/api/auth/callback/google` (local frontend dev) - - `http://localhost:3001/api/auth/callback/google` (Docker Compose) +3. Add to **Authorised redirect URIs**: + - `http://localhost:3000/api/auth/callback/google` (local dev, Docker or bare) - `https://yourdomain.com/api/auth/callback/google` (production) -4. Copy **Client ID** and **Client Secret** — you will need both below +4. Copy **Client ID** and **Client Secret** --- -## Local development with Docker Compose +## Quick start (Docker Compose + Infisical) + +This is the recommended path. Infisical injects secrets at runtime so no secrets are ever +written to disk or committed to git. -### 1. Clone and enter the repository +### 1. Clone ```bash -git clone https://github.com/your-username/spendhound.git +git clone https://github.com/sumdher/spendhound.git cd spendhound ``` -### 2. Generate secrets +### 2. Create an Infisical project and populate secrets + +Sign up at https://infisical.com, create a project, and add the secrets listed in the +[Environment variables reference](#environment-variables-reference) below. Then log in once: + +```bash +infisical login +``` + +### 3. Generate secrets locally (values to paste into Infisical) ```bash # JWT signing key (backend) @@ -79,100 +116,68 @@ python3 -c "import secrets; print(secrets.token_hex(32))" # NextAuth secret (frontend) python3 -c "import secrets; print(secrets.token_hex(32))" -# Fernet key for encrypting user LLM API keys at rest (backend, optional but recommended) +# Fernet key for encrypting user LLM API keys at rest (optional but recommended) python3 -c "from cryptography.fernet import Fernet; print(Fernet.generate_key().decode())" + +# Shared secret for the internal Puppeteer PDF endpoint (required if monthly reports enabled) +python3 -c "import secrets; print(secrets.token_hex(32))" ``` -### 3. Configure environment files +### 4. Start the stack ```bash -cp .env.example .env -cp backend/.env.example backend/.env -cp frontend/.env.example frontend/.env +make dev # foreground (recommended for first run) +make dev-detach # background ``` -**`backend/.env` — required values:** - -```env -GOOGLE_CLIENT_ID= -GOOGLE_CLIENT_SECRET= -JWT_SECRET= -ADMIN_EMAIL= +The `make dev` target runs: +``` +infisical run --env dev --path / --recursive -- docker compose up --build ``` -**`backend/.env` — optional values:** +Alembic migrations run automatically before the backend starts. -```env -# Approval emails (requires a Resend account) -RESEND_API_KEY= -RESEND_FROM_EMAIL= -APP_URL=http://localhost:3001 # or your production URL — used in approval email links - -# LLM providers (Ollama is used by default and requires no key) -OLLAMA_URL=http://host.docker.internal:11434 -OLLAMA_MODEL=gemma4:4b -ANTHROPIC_API_KEY= -ANTHROPIC_MODEL=claude-sonnet-4-20250514 -OPENAI_API_KEY= -OPENAI_MODEL=gpt-4o-mini - -# Encrypt user-supplied LLM API keys stored in the database -LLM_KEY_ENCRYPTION_SECRET= - -# Monthly PDF reports (requires Puppeteer-compatible Chromium in the frontend container) -MONTHLY_REPORTS_ENABLED=false -MONTHLY_REPORTS_FRONTEND_TOKEN= - -# Recurring expense auto-generation -RECURRING_GENERATION_ENABLED=false - -# Rate limits (defaults shown) -RATE_LIMIT_AUTH_PER_MINUTE=10 -RATE_LIMIT_UPLOAD_PER_MINUTE=3 -RATE_LIMIT_CHAT_PER_MINUTE=20 - -# Debug mode — enables /docs, /redoc, /openapi.json; skips startup secret check -DEBUG=false -``` +### 5. Sign in -**`frontend/.env` — required values:** +Open http://localhost:3000, click **Sign in with Google**, and sign in with the email you +configured as `ADMIN_EMAIL`. That account is auto-approved. All other accounts start as +`pending` and require admin approval via the email link sent to `ADMIN_EMAIL`. -```env -GOOGLE_CLIENT_ID= -GOOGLE_CLIENT_SECRET= -NEXTAUTH_SECRET= -NEXTAUTH_URL=http://localhost:3001 # or your production URL -``` +--- -**`frontend/.env` — optional values:** +## Docker Compose services -```env -# Required only if monthly reports are enabled -MONTHLY_REPORTS_FRONTEND_TOKEN= -MONTHLY_REPORTS_FRONTEND_TOKEN_HEADER=X-SpendHound-Internal-Token -MONTHLY_REPORTS_BACKEND_JWT_SECRET= -``` +| Service | Dev URL | Notes | +|---|---|---| +| Frontend | http://localhost:3000 | Next.js app | +| Backend API | http://localhost:8000 | FastAPI; `/docs` only when `DEBUG=true` | +| PostgreSQL | localhost:5432 | pgvector-enabled | +| Redis | (internal only) | Task broker + cache; no host port exposed | +| Celery worker | (no HTTP) | Processes receipt extraction and report tasks | +| Celery Beat | (no HTTP) | Runs scheduled tasks (demo reset, recurring expenses) | +| Flower | http://localhost:5555 | Celery task monitor (dev only) | +| Prometheus | http://localhost:9090 | Metrics scraper | +| Grafana | http://localhost:3004 | Pre-provisioned dashboards; anonymous admin in dev | -### 4. Start the app +--- -```bash -docker compose up --build -``` +## Quick start (manual .env, without Infisical) -Services: +If you prefer not to use Infisical, create the env files manually. -| Service | URL | -|---|---| -| Frontend | http://localhost:3001 | -| Backend API | http://localhost:8000 | -| API docs (debug only) | http://localhost:8000/docs — only when `DEBUG=true` | -| PostgreSQL | localhost:5432 | +```bash +cp backend/.env.example backend/.env +cp frontend/.env.example frontend/.env +``` -The backend container runs `alembic upgrade head` automatically before starting. +Fill in the required values (see the reference tables below), then: -### 5. Sign in +```bash +docker compose up --build +``` -Open http://localhost:3001, click **Sign in with Google**, and sign in with the email you set as `ADMIN_EMAIL`. That account is auto-approved. All other accounts start as `pending` and require admin approval via the email link sent to `ADMIN_EMAIL`. +The Makefile targets (`make dev`, etc.) require Infisical. Use `docker compose` directly +when running without it. --- @@ -185,17 +190,24 @@ cd backend python3 -m venv .venv source .venv/bin/activate pip install -e .[dev] -cp .env.example .env # then fill in the values above +cp .env.example .env # fill in required values alembic upgrade head uvicorn app.main:app --reload --host 0.0.0.0 --port 8000 ``` +Start the Celery worker and beat scheduler in separate terminals: + +```bash +celery -A app.celery_app worker --loglevel=info --concurrency=1 +celery -A app.celery_app beat --loglevel=info --schedule=/tmp/celerybeat-schedule +``` + ### Frontend ```bash cd frontend npm install -cp .env.example .env # then fill in the values above +cp .env.example .env # fill in required values npm run dev # http://localhost:3000 ``` @@ -214,90 +226,185 @@ NEXTAUTH_URL=http://localhost:3000 ```bash cd backend pip install -e .[dev] -pytest tests/test_expenses_crud.py tests/test_parser.py +pytest +``` + +Tests use SQLite in-memory + fakeredis. No external services required. pgvector-specific +code (RAG embeddings) is not covered by the test suite; run the `migrations` CI job +(real PostgreSQL) to verify pgvector migration SQL. + +--- + +## Observability + +### Prometheus + +Custom metrics exposed at `GET /metrics` (requires `Authorization: Bearer `): + +| Metric | Type | Description | +|---|---|---| +| `receipt_queue_depth` | Gauge | Pending tasks in the Celery/Redis queue | +| `llm_response_seconds` | Histogram | LLM `complete()` latency, labelled by provider | +| `rate_limit_hits_total` | Counter | Rejected requests, labelled by endpoint and limit type | +| `http_request_duration_seconds` | Histogram | Standard ASGI metrics via prometheus-fastapi-instrumentator | + +### Grafana + +Pre-provisioned dashboards are in `grafana/provisioning/`. Grafana reads from the +Prometheus datasource automatically. In dev, anonymous admin access is enabled. In +production, set `GRAFANA_ADMIN_PASSWORD` and disable anonymous access. + +In production, Prometheus and Grafana are bound to `127.0.0.1` only. Access via SSH tunnel: + +```bash +ssh -L 9090:localhost:9090 -L 3004:localhost:3004 user@your-server ``` --- +## Demo mode + +SpendHound ships with a public demo account (Bruce Wayne, `bruce.wayne@wayneenterprises.com`) +pre-seeded with 50+ expenses, budgets, categories, and chat history. + +- Click **Try demo** on the login page -- no Google account required +- All changes are wiped and the seed data is restored every 30 minutes on the hour + (at `:00` and `:30`) by a Celery Beat task +- The demo account cannot use the server's Ollama instance; a personal API key is required + to use AI features in the demo + +To enable demo mode, set `DEMO_USER_EMAIL=bruce.wayne@wayneenterprises.com` in your +backend config. The demo reset task is always scheduled in Celery Beat; it is a no-op if +no demo user exists in the database. + +--- + +## CI/CD pipeline + +Two GitHub Actions workflows are in `.github/workflows/`. + +### CI (`ci.yml`) + +Triggers on every pull request to `main` and every push to `main`. Four jobs run in +parallel on GitHub-hosted `ubuntu-latest` runners: + +| Job | What it does | +|---|---| +| `backend` | `ruff check` + `mypy` + `pytest` (SQLite + fakeredis) | +| `migrations` | Alembic round-trip against real `pgvector/pgvector:pg16`: upgrade -> downgrade to 0007 -> upgrade | +| `frontend` | ESLint + `tsc --noEmit` | +| `docker-build` | `docker build --target production` for both backend and frontend | + +### CD (`cd.yml`) + +Triggers only after `ci.yml` completes successfully on `main`. Runs on a self-hosted +runner on the production server. + +Steps: +1. Authenticates with Infisical using short-lived machine credentials +2. Pulls the validated commit from git +3. Runs `infisical run --env prod -- docker compose -f docker-compose.prod.yml up --build --force-recreate -d` +4. Polls `docker inspect` for `spendhound_backend_prod` health for up to 2 minutes; dumps + logs and exits 1 if the container becomes unhealthy or the timeout is reached + +The CD job is gated behind a GitHub environment (`production`) that requires a manual +approval click before it runs. + +### Branch protection (main) + +- All four CI jobs must pass before merge +- PR required before merge; up-to-date branches enforced +- No force pushes; no branch deletion +- Any PR from a non-collaborator requires manual approval before workflows run +- `.github/CODEOWNERS` requires `@sumdher` review on any change to workflow files + +--- + ## Production deployment ### Docker Compose (recommended) -A production-ready Compose file is provided at [`docker-compose.prod.yml`](docker-compose.prod.yml). It pins service versions, disables the dev volume mounts, and expects a `.env` at the repository root for shared credentials. +A production Compose file is at [`docker-compose.prod.yml`](docker-compose.prod.yml). Key +differences from dev: pinned image digests, no volume-mounted source code, `--target +production` build stages, resource limits on every container, all capabilities dropped, +`no-new-privileges:true`. ```bash -docker compose -f docker-compose.prod.yml up -d --build +make prod # infisical run --env prod -- docker compose -f docker-compose.prod.yml up --build -d +make prod-recreate # force-recreate (not zero-downtime) +make prod-logs # follow logs ``` -For HTTPS without a reverse proxy, an optional Cloudflare Tunnel service is included — add your `CLOUDFLARE_TUNNEL_TOKEN` to the root `.env`. +### Public ingress + +The prod stack includes a `cloudflared` sidecar that establishes a Cloudflare Tunnel. +Set `CLOUDFLARE_TUNNEL_TOKEN` in Infisical and configure your Cloudflare dashboard to +route your domain to `http://frontend:3000`. No port 443 needs to be open on the host. -### PostgreSQL backups (systemd) +### PostgreSQL backups -A host-side systemd backup setup lives under [`deploy/backup/`](deploy/backup/). Run this once on the production host from the repository root: +A host-side systemd backup setup is in [`deploy/backup/`](deploy/backup/). Run once on +the production host: ```bash sudo bash ./deploy/backup/install-spendhound-db-backup.sh ``` -This installs and enables a systemd timer that: - -- runs daily at 03:15 UTC (with up to 15 min randomised delay; `Persistent=true` catches missed runs) -- dumps the running `db` container with `pg_dump` -- writes a compressed archive to a temp file, validates it with `pg_restore --list`, writes a SHA-256 checksum, then atomically renames both into place -- prunes old backups after the configured retention period -- runs with `set -Eeuo pipefail`, `umask 077`, and `flock` to prevent overlapping runs +This installs a systemd timer that: +- Runs daily at 03:15 UTC (`Persistent=true` catches missed runs) +- Dumps the `db` container with `pg_dump` +- Validates the archive with `pg_restore --list` and writes a SHA-256 checksum +- Atomically renames the validated archive into place +- Prunes old backups after the configured retention period +- Uses `set -Eeuo pipefail`, `umask 077`, and `flock` to prevent overlapping runs -Backups are stored in `/var/backups/spendhound`. Useful commands: +Backups are stored in `/var/backups/spendhound`. ```bash sudo systemctl status spendhound-db-backup.timer --no-pager -sudo systemctl list-timers spendhound-db-backup.timer sudo journalctl -u spendhound-db-backup.service -n 50 --no-pager ``` -### LLM concurrency (single-machine / single-GPU) +### Single-worker constraint -SpendHound is designed for a single-worker deployment (`--workers 1`). The in-process `asyncio.Semaphore` that serialises Ollama calls and the in-memory `slowapi` rate counters both require a single process. If you scale to multiple workers, replace the in-memory rate limiter with a Redis backend and the semaphore with a distributed lock. - -Key concurrency settings in `backend/.env`: - -```env -OLLAMA_MAX_CONCURRENT=1 # GPU semaphore width; increase only for CPU or multi-GPU -LLM_SEMAPHORE_WAIT_TIMEOUT=5.0 # Seconds to wait before returning HTTP 503 -LLM_TIMEOUT_SECONDS=120 # Total timeout per LLM call -RECEIPT_QUEUE_MAXSIZE=10 # Max queued receipt extraction jobs -DB_POOL_SIZE=20 -DB_MAX_OVERFLOW=40 -``` +SpendHound is designed for `--workers 1`. The in-process `asyncio.Semaphore` that +serializes Ollama calls and the in-memory slowapi rate counters both require a single +process. To scale beyond one worker, replace the in-memory rate limiter with a Redis +backend and the semaphore with a distributed lock. --- ## Security -SpendHound applies defence-in-depth across the full stack. - ### Secrets -- `JWT_SECRET` must be a strong random value. The backend **raises `RuntimeError` and refuses to start** in production (`DEBUG=false`) if the default placeholder is detected. -- User LLM API keys are Fernet-encrypted at rest; never returned in API responses (only a boolean `has_llm_api_key` is surfaced). -- `LLM_KEY_ENCRYPTION_SECRET` should be set to a Fernet key (see step 2 above). Without it, user-supplied API keys are stored unencrypted. +- `JWT_SECRET` must be a strong random value. The backend raises `RuntimeError` and + refuses to start in production (`DEBUG=false`) if the default placeholder is detected. +- User LLM API keys are Fernet-encrypted at rest; never returned in API responses (only + a boolean `has_llm_api_key` is surfaced). +- `LLM_KEY_ENCRYPTION_SECRET` must be set to a Fernet key. Without it, user-supplied API + keys are stored unencrypted. ### API surface -- `/docs`, `/redoc`, and `/openapi.json` are only mounted when `DEBUG=true`. In production the full API schema is hidden. -- User search (`/api/auth/users/search`) requires a minimum query length of 3 characters to prevent single-character enumeration of user accounts. +- `/docs`, `/redoc`, and `/openapi.json` are only mounted when `DEBUG=true`. +- User search (`/api/auth/users/search`) requires a minimum query length of 3 characters + to prevent single-character enumeration of accounts. +- `/metrics` requires `Authorization: Bearer `. ### File uploads Uploads pass three validation layers before anything reaches disk: -1. **Extension allowlist** — only `.jpg`, `.jpeg`, `.png`, `.gif`, `.bmp`, `.webp`, `.pdf` accepted; unknown extensions stored as `.bin`. -2. **Magic-byte verification** — actual file header bytes are checked against known signatures; a `.jpg` file with non-JPEG content is rejected with HTTP 400. -3. **Size cap** — 50 MB hard limit enforced before any I/O; returns HTTP 413. +1. **Extension allowlist** -- only `.jpg`, `.jpeg`, `.png`, `.gif`, `.bmp`, `.webp`, `.pdf` accepted +2. **Magic-byte verification** -- actual file header bytes are checked; mismatched content rejected with HTTP 400 +3. **Size cap** -- 50 MB hard limit enforced before any I/O; returns HTTP 413 -### Bot / automation blocking +### Bot blocking -A `block_bots` dependency is applied to `POST /api/auth/google` and `POST /api/receipts/upload`. It rejects empty `User-Agent` headers and known non-browser client signatures, returning HTTP 403 before rate-limit counters are consumed. +A `block_bots` dependency is applied to `POST /api/auth/google` and +`POST /api/receipts/upload`. It rejects empty `User-Agent` headers and known non-browser +client signatures before rate-limit counters are consumed. ### HTTP security headers @@ -310,25 +417,37 @@ All frontend routes include: | `Referrer-Policy` | `strict-origin-when-cross-origin` | | `Permissions-Policy` | `camera=(), microphone=(), geolocation=(), payment=()` | | `Strict-Transport-Security` | `max-age=63072000; includeSubDomains; preload` | -| `Content-Security-Policy` | `default-src 'self'`; `frame-ancestors 'none'`; `base-uri 'self'`; `form-action 'self'` | +| `Content-Security-Policy` | `default-src 'self'; frame-ancestors 'none'; base-uri 'self'; form-action 'self'` | ### Prompt injection hardening -- **Chat:** All user-controlled data (merchant names, expense descriptions, receipt filenames, session titles, chat history) is wrapped in `` XML delimiters in the LLM context. The system prompt explicitly instructs the model to treat inner content as untrusted read-only data, never as instructions. -- **Receipt extraction:** Per-user prompt overrides are sandboxed — an immutable role anchor is prepended before any user-supplied text, wrapped in `` tags. A malicious override cannot change the model's role or produce non-JSON output. +- **Chat:** All user-controlled data (merchant names, expense descriptions, receipt + filenames, session titles, chat history) is wrapped in `...` XML + delimiters. The system prompt instructs the model to treat inner content as untrusted + read-only data, never as instructions. +- **Receipt extraction:** Per-user prompt overrides are sandboxed -- an immutable role + anchor is prepended before any user-supplied text, wrapped in `` + tags. A malicious override cannot change the model role or produce non-JSON output. --- ## Receipt extraction flow 1. Open **Add expense** and switch to the **Upload receipt** tab -2. SpendHound validates the file (extension, magic bytes, size), stores it under `storage/receipts/{user_id}/` -3. The upload returns immediately; extraction is queued into a bounded `asyncio.Queue` and processed by a background worker -4. For images ≤ 7.5 MB the raw image is sent to the configured multimodal LLM; larger files fall back to OCR text -5. The extracted JSON is validated against the `ReceiptPreviewModel` schema; confidence < 0.75 flags the receipt for review -6. The user reviews and edits the draft +2. SpendHound validates the file (extension, magic bytes, size) and saves it under + `storage/receipts/{user_id}/` +3. The upload endpoint enqueues a Celery task and returns immediately +4. The Celery worker picks up the task from the Redis queue (`celery` list key). For + images <= 7.5 MB the raw image is sent to the configured multimodal LLM; larger + files fall back to OCR text +5. The extracted JSON is validated against the `ReceiptPreviewModel` schema; confidence + below 0.75 flags the receipt as `needs_review` +6. The user reviews and edits the draft in the UI 7. Only the confirmed payload creates an expense record +If the Celery worker is not running, uploaded receipts remain in `status="pending"` +indefinitely and are never processed. + --- ## Key app routes @@ -338,10 +457,12 @@ All frontend routes include: | `/dashboard` | Monthly analytics overview | | `/expenses` | Full expense list with filters | | `/expenses/new` | Manual entry or receipt upload | +| `/receipts` | Upload and review queue | | `/budgets` | Budget management | | `/categories` | Category, rule, and knowledge-base management | +| `/rules` | Merchant and item keyword rules | | `/chat` | AI financial chat | -| `/settings` | LLM provider settings and account | +| `/account` | LLM provider settings, monthly reports toggle, receipt prompt override | | `/admin` | User approval panel (admin only) | --- @@ -353,28 +474,34 @@ All frontend routes include: | Variable | Required | Default | Description | |---|---|---|---| | `DATABASE_URL` | yes | (Docker default) | Async PostgreSQL connection string | -| `GOOGLE_CLIENT_ID` | yes | — | Google OAuth client ID | -| `GOOGLE_CLIENT_SECRET` | yes | — | Google OAuth client secret | -| `JWT_SECRET` | yes | — | HS256 JWT signing key; must not be the default placeholder | -| `ADMIN_EMAIL` | yes | — | Auto-approved on sign-in; receives approval request emails | +| `REDIS_URL` | yes | `redis://localhost:6379/0` | Redis connection string (broker + cache) | +| `GOOGLE_CLIENT_ID` | yes | -- | Google OAuth client ID | +| `GOOGLE_CLIENT_SECRET` | yes | -- | Google OAuth client secret | +| `JWT_SECRET` | yes | -- | HS256 JWT signing key; must not be the default placeholder in production | +| `ADMIN_EMAIL` | yes | -- | Auto-approved on sign-in; receives approval request emails | | `APP_URL` | yes | `http://localhost:3000` | Public frontend URL (used in email links) | -| `LLM_PROVIDER` | no | `ollama` | `ollama` / `openai` / `anthropic` / `nebius` | +| `LLM_PROVIDER` | no | `ollama` | `ollama` / `openai` / `anthropic` / `nebius` / `openrouter` / `groq` / `together` / `mistral` / `deepseek` | | `OLLAMA_URL` | no | `http://host.docker.internal:11434` | Ollama base URL | | `OLLAMA_MODEL` | no | `gemma4:4b` | Ollama model name | -| `ANTHROPIC_API_KEY` | no | — | Required when `LLM_PROVIDER=anthropic` | -| `OPENAI_API_KEY` | no | — | Required when `LLM_PROVIDER=openai` | -| `LLM_KEY_ENCRYPTION_SECRET` | no | — | Fernet key for encrypting user API keys at rest | -| `RESEND_API_KEY` | no | — | Enables approval and report emails | -| `RESEND_FROM_EMAIL` | no | — | Sender address for Resend | +| `ANTHROPIC_API_KEY` | no | -- | Required when `LLM_PROVIDER=anthropic` | +| `ANTHROPIC_MODEL` | no | `claude-sonnet-4-20250514` | Anthropic model ID | +| `OPENAI_API_KEY` | no | -- | Required when `LLM_PROVIDER=openai` | +| `OPENAI_MODEL` | no | `gpt-4o-mini` | OpenAI model ID | +| `DEEPSEEK_API_KEY` | no | -- | Required when `LLM_PROVIDER=deepseek` | +| `NEBIUS_API_KEY` | no | -- | Required when `LLM_PROVIDER=nebius` | +| `LLM_KEY_ENCRYPTION_SECRET` | no | -- | Fernet key for encrypting user API keys at rest | +| `METRICS_TOKEN` | no | -- | Bearer token required to scrape `GET /metrics` | +| `RESEND_API_KEY` | no | -- | Enables approval and report emails | +| `RESEND_FROM_EMAIL` | no | -- | Sender address for Resend | | `MONTHLY_REPORTS_ENABLED` | no | `false` | Enable scheduled monthly PDF delivery | -| `MONTHLY_REPORTS_FRONTEND_TOKEN` | no | — | Shared secret for the internal Puppeteer PDF endpoint; required when reports are enabled | +| `MONTHLY_REPORTS_FRONTEND_TOKEN` | no | -- | Shared secret for the internal Puppeteer PDF endpoint; required when reports are enabled | | `RECURRING_GENERATION_ENABLED` | no | `false` | Enable auto-generation of recurring expenses | | `RECEIPT_REVIEW_CONFIDENCE_THRESHOLD` | no | `0.75` | Extractions below this are flagged for review | | `RECEIPT_MULTIMODAL_MAX_BYTES` | no | `7500000` | Images above this size use OCR instead of direct multimodal | -| `OLLAMA_MAX_CONCURRENT` | no | `1` | GPU semaphore width | +| `OLLAMA_MAX_CONCURRENT` | no | `1` | GPU semaphore width; increase only for CPU or multi-GPU | | `LLM_SEMAPHORE_WAIT_TIMEOUT` | no | `5.0` | Seconds before returning HTTP 503 on a busy LLM | | `LLM_TIMEOUT_SECONDS` | no | `120` | Total timeout per LLM call | -| `RECEIPT_QUEUE_MAXSIZE` | no | `10` | Max queued extraction jobs | +| `RECEIPT_QUEUE_MAXSIZE` | no | `10` | Maximum pending tasks before uploads are rejected | | `RATE_LIMIT_AUTH_PER_MINUTE` | no | `10` | Auth requests per IP per minute | | `RATE_LIMIT_UPLOAD_PER_MINUTE` | no | `3` | Receipt uploads per user per minute | | `RATE_LIMIT_CHAT_PER_MINUTE` | no | `20` | Chat requests per user per minute | @@ -389,8 +516,31 @@ All frontend routes include: | `GOOGLE_CLIENT_ID` | yes | Same Google OAuth client ID as backend | | `GOOGLE_CLIENT_SECRET` | yes | Same Google OAuth client secret as backend | | `NEXTAUTH_SECRET` | yes | Random secret for NextAuth session signing | -| `NEXTAUTH_URL` | yes | Public frontend URL (e.g. `http://localhost:3001`) | +| `NEXTAUTH_URL` | yes | Public frontend URL (e.g. `http://localhost:3000`) | | `NEXT_PUBLIC_API_URL` | no (non-Docker) | Backend API base URL for browser requests | | `INTERNAL_API_URL` | no (non-Docker) | Backend API base URL for server-side requests | | `MONTHLY_REPORTS_FRONTEND_TOKEN` | no | Must match backend value when reports are enabled | | `MONTHLY_REPORTS_BACKEND_JWT_SECRET` | no | Must match `JWT_SECRET` in backend when reports are enabled | + +### Production-only variables + +| Variable | Where | Description | +|---|---|---| +| `POSTGRES_PASSWORD` | root `.env` / Infisical | PostgreSQL superuser password | +| `GRAFANA_ADMIN_PASSWORD` | root `.env` / Infisical | Grafana admin password (anonymous access disabled in prod) | +| `CLOUDFLARE_TUNNEL_TOKEN` | root `.env` / Infisical | Cloudflare Tunnel token for HTTPS ingress | +| `FRONTEND_PORT` | root `.env` / Infisical | Host port for the frontend container (default `3002` in prod) | + +--- + +## License + +SpendHound 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. + +See [LICENSE](LICENSE) for the full license text. + +Under the AGPL v3, if you run a modified version of SpendHound as a network service +accessible to others, you must make your modified source code available under the same +license. diff --git a/knowledge_base_examplecsv b/knowledge_base_example.csv similarity index 100% rename from knowledge_base_examplecsv rename to knowledge_base_example.csv