├── .github ├── images │ └── logo.png └── workflows │ ├── build.yml │ ├── server-rex-deploy.yml │ └── website-rex-deploy.yml ├── CHANGELOG.md ├── LICENSE ├── Makefile ├── README.md ├── actions ├── action.go ├── add_file_action.go ├── delete_user_action.go ├── download_files_action.go ├── list_files_action.go ├── login_action.go ├── remove_file_action.go └── upload_files_action.go ├── config └── config.go ├── go.mod ├── main.go ├── server ├── .dockerignore ├── .env.example ├── .formatter.exs ├── .gitignore ├── CHANGELOG.md ├── Dockerfile ├── LICENSE ├── README.md ├── config │ ├── config.exs │ ├── dev.exs │ ├── prod.exs │ ├── runtime.exs │ └── test.exs ├── docker-compose.yml ├── docker-entrypoint.sh ├── lib │ ├── dotsync.ex │ ├── dotsync │ │ ├── application.ex │ │ ├── jwt.ex │ │ ├── mailer.ex │ │ ├── repo.ex │ │ ├── schemas │ │ │ ├── file.ex │ │ │ └── user.ex │ │ └── services │ │ │ ├── file.ex │ │ │ └── user.ex │ ├── dotsync_web.ex │ └── dotsync_web │ │ ├── controllers │ │ ├── error_json.ex │ │ ├── file.ex │ │ └── user.ex │ │ ├── endpoint.ex │ │ ├── gettext.ex │ │ ├── router.ex │ │ └── telemetry.ex ├── mix.exs ├── mix.lock ├── priv │ ├── gettext │ │ ├── en │ │ │ └── LC_MESSAGES │ │ │ │ └── errors.po │ │ └── errors.pot │ ├── repo │ │ ├── migrations │ │ │ ├── .formatter.exs │ │ │ ├── 20230817134607_initial.exs │ │ │ ├── 20230820223642_increase_file_content_length.exs │ │ │ └── 20230823091359_increase_file_content_length_again.exs │ │ └── seeds.exs │ └── static │ │ ├── favicon.ico │ │ └── robots.txt └── test │ ├── dotsync_web │ └── controllers │ │ └── error_json_test.exs │ ├── support │ ├── conn_case.ex │ └── data_case.ex │ └── test_helper.exs ├── utils ├── configfile │ └── configfile.go └── json │ └── json.go └── website ├── .dockerignore ├── .env.example ├── .gitignore ├── .npmrc ├── .prettierignore ├── .prettierrc ├── CHANGELOG.md ├── Dockerfile ├── LICENSE ├── README.md ├── docker-compose.yml ├── package-lock.json ├── package.json ├── postcss.config.cjs ├── src ├── app.d.ts ├── app.html ├── app.postcss ├── lib │ ├── components │ │ ├── ButtonLink.svelte │ │ ├── CodeSnippet.svelte │ │ ├── DocsIntro.svelte │ │ ├── Footer.svelte │ │ ├── Intro.svelte │ │ └── Section.svelte │ └── index.ts └── routes │ ├── +layout.svelte │ ├── +page.svelte │ ├── api │ └── latest-release │ │ └── +server.ts │ └── docs │ └── +page.svelte ├── static ├── favicon.png ├── fonts │ ├── TerminusTTF-4.49.3.ttf │ ├── TerminusTTF-Bold-4.49.3.ttf │ └── TerminusTTF-Italic-4.49.3.ttf └── logo.png ├── svelte.config.js ├── tailwind.config.cjs ├── tsconfig.json └── vite.config.ts /.github/images/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mbaraa/dotsync/e6c2f5eed6bcaad12f893ff735c75ec7f97f2183/.github/images/logo.png -------------------------------------------------------------------------------- /.github/workflows/build.yml: -------------------------------------------------------------------------------- 1 | name: build 2 | 3 | on: 4 | push: 5 | branches: 6 | - main 7 | paths-ignore: 8 | - "**.md" 9 | - "website/**" 10 | - "server/**" 11 | pull_request: 12 | paths-ignore: 13 | - "**.md" 14 | - "website/**" 15 | - "server/**" 16 | 17 | jobs: 18 | build: 19 | runs-on: ubuntu-latest 20 | steps: 21 | - uses: actions/checkout@v3 22 | 23 | - name: Set up Go ${{ matrix.go-version }} 24 | uses: actions/setup-go@v3 25 | with: 26 | go-version: ${{ matrix.go-version }} 27 | 28 | - name: Install dependencies 29 | run: | 30 | go get . 31 | 32 | - name: Build 33 | run: go build -v ./... 34 | -------------------------------------------------------------------------------- /.github/workflows/server-rex-deploy.yml: -------------------------------------------------------------------------------- 1 | name: server-rex-deploy 2 | 3 | on: 4 | push: 5 | branches: 6 | - main 7 | paths: 8 | - "server/**" 9 | pull_request: 10 | paths: 11 | - "server/**" 12 | 13 | jobs: 14 | rex-deploy: 15 | runs-on: ubuntu-latest 16 | steps: 17 | - name: rex-7567-e27 18 | uses: mbaraa/rex-action@v1.0 19 | with: 20 | server-url: ${{ secrets.REX_SERVER }} 21 | token: ${{ secrets.REX_KEY }} 22 | repo-name: dotsync/server 23 | -------------------------------------------------------------------------------- /.github/workflows/website-rex-deploy.yml: -------------------------------------------------------------------------------- 1 | name: website-rex-deploy 2 | 3 | on: 4 | push: 5 | branches: 6 | - main 7 | paths: 8 | - "website/**" 9 | pull_request: 10 | paths: 11 | - "website/**" 12 | 13 | jobs: 14 | rex-deploy: 15 | runs-on: ubuntu-latest 16 | steps: 17 | - name: rex-7567-e27 18 | uses: mbaraa/rex-action@v1.0 19 | with: 20 | server-url: ${{ secrets.REX_SERVER }} 21 | token: ${{ secrets.REX_KEY }} 22 | repo-name: dotsync/website 23 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Changelog 2 | 3 | All notable changes to this project will be documented in this file. 4 | 5 | The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), 6 | and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). 7 | 8 | ## \[[v0.1.2](https://github.com/mbaraa/dotsync/releases/tag/v0.1.2)\] 2023-08-28 9 | 10 | ### Added 11 | 12 | - utilizing the new split JWT that's less suspecious to email clients 13 | 14 | ## \[[v0.1.1](https://github.com/mbaraa/dotsync/releases/tag/v0.1.1)\] 2023-08-26 15 | 16 | ### Fixed 17 | 18 | - gosite issues 19 | 20 | 21 | ## \[[v0.1.0](https://github.com/mbaraa/dotsync/releases/tag/v0.1.0)\] 2023-08-26 22 | 23 | The first usable release of Dotsync 🚀 24 | 25 | Includes the following functionalities: 26 | - add files/directories to the sync list 27 | - remove files/directories from the sync list 28 | - update synced files/directories 29 | - download synced files/directories 30 | - login/create user 31 | - delete user 32 | 33 | ## \[[v0.0.4](https://github.com/mbaraa/dotsync/releases/tag/v0.0.4)\] 2023-08-25 34 | 35 | ### Fixed 36 | 37 | - removed file check before remving it from the sync list 38 | 39 | ## \[[v0.0.3](https://github.com/mbaraa/dotsync/releases/tag/v0.0.3)\] 2023-08-24 40 | 41 | ### Added 42 | 43 | - add directories to the sync list 44 | 45 | ## \[[v0.0.2](https://github.com/mbaraa/dotsync/releases/tag/v0.0.2)\] 2023-08-23 46 | 47 | ### Fixed 48 | 49 | - config file path 50 | 51 | ## \[[v0.0.1](https://github.com/mbaraa/dotsync/releases/tag/v0.0.1)\] 2023-08-23 52 | 53 | ### Added 54 | 55 | - an initial version of the client 56 | - add files to the sync list 57 | - remove files from the sync list 58 | - update synced files 59 | - download synced files 60 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | GNU GENERAL PUBLIC LICENSE 2 | Version 3, 29 June 2007 3 | 4 | Copyright (C) 2007 Free Software Foundation, Inc. 5 | Everyone is permitted to copy and distribute verbatim copies 6 | of this license document, but changing it is not allowed. 7 | 8 | Preamble 9 | 10 | The GNU General Public License is a free, copyleft license for 11 | software and other kinds of works. 12 | 13 | The licenses for most software and other practical works are designed 14 | to take away your freedom to share and change the works. By contrast, 15 | the GNU General Public License is intended to guarantee your freedom to 16 | share and change all versions of a program--to make sure it remains free 17 | software for all its users. We, the Free Software Foundation, use the 18 | GNU General Public License for most of our software; it applies also to 19 | any other work released this way by its authors. You can apply it to 20 | your programs, too. 21 | 22 | When we speak of free software, we are referring to freedom, not 23 | price. Our General Public Licenses are designed to make sure that you 24 | have the freedom to distribute copies of free software (and charge for 25 | them if you wish), that you receive source code or can get it if you 26 | want it, that you can change the software or use pieces of it in new 27 | free programs, and that you know you can do these things. 28 | 29 | To protect your rights, we need to prevent others from denying you 30 | these rights or asking you to surrender the rights. Therefore, you have 31 | certain responsibilities if you distribute copies of the software, or if 32 | you modify it: responsibilities to respect the freedom of others. 33 | 34 | For example, if you distribute copies of such a program, whether 35 | gratis or for a fee, you must pass on to the recipients the same 36 | freedoms that you received. You must make sure that they, too, receive 37 | or can get the source code. And you must show them these terms so they 38 | know their rights. 39 | 40 | Developers that use the GNU GPL protect your rights with two steps: 41 | (1) assert copyright on the software, and (2) offer you this License 42 | giving you legal permission to copy, distribute and/or modify it. 43 | 44 | For the developers' and authors' protection, the GPL clearly explains 45 | that there is no warranty for this free software. For both users' and 46 | authors' sake, the GPL requires that modified versions be marked as 47 | changed, so that their problems will not be attributed erroneously to 48 | authors of previous versions. 49 | 50 | Some devices are designed to deny users access to install or run 51 | modified versions of the software inside them, although the manufacturer 52 | can do so. This is fundamentally incompatible with the aim of 53 | protecting users' freedom to change the software. The systematic 54 | pattern of such abuse occurs in the area of products for individuals to 55 | use, which is precisely where it is most unacceptable. Therefore, we 56 | have designed this version of the GPL to prohibit the practice for those 57 | products. If such problems arise substantially in other domains, we 58 | stand ready to extend this provision to those domains in future versions 59 | of the GPL, as needed to protect the freedom of users. 60 | 61 | Finally, every program is threatened constantly by software patents. 62 | States should not allow patents to restrict development and use of 63 | software on general-purpose computers, but in those that do, we wish to 64 | avoid the special danger that patents applied to a free program could 65 | make it effectively proprietary. To prevent this, the GPL assures that 66 | patents cannot be used to render the program non-free. 67 | 68 | The precise terms and conditions for copying, distribution and 69 | modification follow. 70 | 71 | TERMS AND CONDITIONS 72 | 73 | 0. Definitions. 74 | 75 | "This License" refers to version 3 of the GNU General Public License. 76 | 77 | "Copyright" also means copyright-like laws that apply to other kinds of 78 | works, such as semiconductor masks. 79 | 80 | "The Program" refers to any copyrightable work licensed under this 81 | License. Each licensee is addressed as "you". "Licensees" and 82 | "recipients" may be individuals or organizations. 83 | 84 | To "modify" a work means to copy from or adapt all or part of the work 85 | in a fashion requiring copyright permission, other than the making of an 86 | exact copy. The resulting work is called a "modified version" of the 87 | earlier work or a work "based on" the earlier work. 88 | 89 | A "covered work" means either the unmodified Program or a work based 90 | on the Program. 91 | 92 | To "propagate" a work means to do anything with it that, without 93 | permission, would make you directly or secondarily liable for 94 | infringement under applicable copyright law, except executing it on a 95 | computer or modifying a private copy. Propagation includes copying, 96 | distribution (with or without modification), making available to the 97 | public, and in some countries other activities as well. 98 | 99 | To "convey" a work means any kind of propagation that enables other 100 | parties to make or receive copies. Mere interaction with a user through 101 | a computer network, with no transfer of a copy, is not conveying. 102 | 103 | An interactive user interface displays "Appropriate Legal Notices" 104 | to the extent that it includes a convenient and prominently visible 105 | feature that (1) displays an appropriate copyright notice, and (2) 106 | tells the user that there is no warranty for the work (except to the 107 | extent that warranties are provided), that licensees may convey the 108 | work under this License, and how to view a copy of this License. If 109 | the interface presents a list of user commands or options, such as a 110 | menu, a prominent item in the list meets this criterion. 111 | 112 | 1. Source Code. 113 | 114 | The "source code" for a work means the preferred form of the work 115 | for making modifications to it. "Object code" means any non-source 116 | form of a work. 117 | 118 | A "Standard Interface" means an interface that either is an official 119 | standard defined by a recognized standards body, or, in the case of 120 | interfaces specified for a particular programming language, one that 121 | is widely used among developers working in that language. 122 | 123 | The "System Libraries" of an executable work include anything, other 124 | than the work as a whole, that (a) is included in the normal form of 125 | packaging a Major Component, but which is not part of that Major 126 | Component, and (b) serves only to enable use of the work with that 127 | Major Component, or to implement a Standard Interface for which an 128 | implementation is available to the public in source code form. A 129 | "Major Component", in this context, means a major essential component 130 | (kernel, window system, and so on) of the specific operating system 131 | (if any) on which the executable work runs, or a compiler used to 132 | produce the work, or an object code interpreter used to run it. 133 | 134 | The "Corresponding Source" for a work in object code form means all 135 | the source code needed to generate, install, and (for an executable 136 | work) run the object code and to modify the work, including scripts to 137 | control those activities. However, it does not include the work's 138 | System Libraries, or general-purpose tools or generally available free 139 | programs which are used unmodified in performing those activities but 140 | which are not part of the work. For example, Corresponding Source 141 | includes interface definition files associated with source files for 142 | the work, and the source code for shared libraries and dynamically 143 | linked subprograms that the work is specifically designed to require, 144 | such as by intimate data communication or control flow between those 145 | subprograms and other parts of the work. 146 | 147 | The Corresponding Source need not include anything that users 148 | can regenerate automatically from other parts of the Corresponding 149 | Source. 150 | 151 | The Corresponding Source for a work in source code form is that 152 | same work. 153 | 154 | 2. Basic Permissions. 155 | 156 | All rights granted under this License are granted for the term of 157 | copyright on the Program, and are irrevocable provided the stated 158 | conditions are met. This License explicitly affirms your unlimited 159 | permission to run the unmodified Program. The output from running a 160 | covered work is covered by this License only if the output, given its 161 | content, constitutes a covered work. This License acknowledges your 162 | rights of fair use or other equivalent, as provided by copyright law. 163 | 164 | You may make, run and propagate covered works that you do not 165 | convey, without conditions so long as your license otherwise remains 166 | in force. You may convey covered works to others for the sole purpose 167 | of having them make modifications exclusively for you, or provide you 168 | with facilities for running those works, provided that you comply with 169 | the terms of this License in conveying all material for which you do 170 | not control copyright. Those thus making or running the covered works 171 | for you must do so exclusively on your behalf, under your direction 172 | and control, on terms that prohibit them from making any copies of 173 | your copyrighted material outside their relationship with you. 174 | 175 | Conveying under any other circumstances is permitted solely under 176 | the conditions stated below. Sublicensing is not allowed; section 10 177 | makes it unnecessary. 178 | 179 | 3. Protecting Users' Legal Rights From Anti-Circumvention Law. 180 | 181 | No covered work shall be deemed part of an effective technological 182 | measure under any applicable law fulfilling obligations under article 183 | 11 of the WIPO copyright treaty adopted on 20 December 1996, or 184 | similar laws prohibiting or restricting circumvention of such 185 | measures. 186 | 187 | When you convey a covered work, you waive any legal power to forbid 188 | circumvention of technological measures to the extent such circumvention 189 | is effected by exercising rights under this License with respect to 190 | the covered work, and you disclaim any intention to limit operation or 191 | modification of the work as a means of enforcing, against the work's 192 | users, your or third parties' legal rights to forbid circumvention of 193 | technological measures. 194 | 195 | 4. Conveying Verbatim Copies. 196 | 197 | You may convey verbatim copies of the Program's source code as you 198 | receive it, in any medium, provided that you conspicuously and 199 | appropriately publish on each copy an appropriate copyright notice; 200 | keep intact all notices stating that this License and any 201 | non-permissive terms added in accord with section 7 apply to the code; 202 | keep intact all notices of the absence of any warranty; and give all 203 | recipients a copy of this License along with the Program. 204 | 205 | You may charge any price or no price for each copy that you convey, 206 | and you may offer support or warranty protection for a fee. 207 | 208 | 5. Conveying Modified Source Versions. 209 | 210 | You may convey a work based on the Program, or the modifications to 211 | produce it from the Program, in the form of source code under the 212 | terms of section 4, provided that you also meet all of these conditions: 213 | 214 | a) The work must carry prominent notices stating that you modified 215 | it, and giving a relevant date. 216 | 217 | b) The work must carry prominent notices stating that it is 218 | released under this License and any conditions added under section 219 | 7. This requirement modifies the requirement in section 4 to 220 | "keep intact all notices". 221 | 222 | c) You must license the entire work, as a whole, under this 223 | License to anyone who comes into possession of a copy. This 224 | License will therefore apply, along with any applicable section 7 225 | additional terms, to the whole of the work, and all its parts, 226 | regardless of how they are packaged. This License gives no 227 | permission to license the work in any other way, but it does not 228 | invalidate such permission if you have separately received it. 229 | 230 | d) If the work has interactive user interfaces, each must display 231 | Appropriate Legal Notices; however, if the Program has interactive 232 | interfaces that do not display Appropriate Legal Notices, your 233 | work need not make them do so. 234 | 235 | A compilation of a covered work with other separate and independent 236 | works, which are not by their nature extensions of the covered work, 237 | and which are not combined with it such as to form a larger program, 238 | in or on a volume of a storage or distribution medium, is called an 239 | "aggregate" if the compilation and its resulting copyright are not 240 | used to limit the access or legal rights of the compilation's users 241 | beyond what the individual works permit. Inclusion of a covered work 242 | in an aggregate does not cause this License to apply to the other 243 | parts of the aggregate. 244 | 245 | 6. Conveying Non-Source Forms. 246 | 247 | You may convey a covered work in object code form under the terms 248 | of sections 4 and 5, provided that you also convey the 249 | machine-readable Corresponding Source under the terms of this License, 250 | in one of these ways: 251 | 252 | a) Convey the object code in, or embodied in, a physical product 253 | (including a physical distribution medium), accompanied by the 254 | Corresponding Source fixed on a durable physical medium 255 | customarily used for software interchange. 256 | 257 | b) Convey the object code in, or embodied in, a physical product 258 | (including a physical distribution medium), accompanied by a 259 | written offer, valid for at least three years and valid for as 260 | long as you offer spare parts or customer support for that product 261 | model, to give anyone who possesses the object code either (1) a 262 | copy of the Corresponding Source for all the software in the 263 | product that is covered by this License, on a durable physical 264 | medium customarily used for software interchange, for a price no 265 | more than your reasonable cost of physically performing this 266 | conveying of source, or (2) access to copy the 267 | Corresponding Source from a network server at no charge. 268 | 269 | c) Convey individual copies of the object code with a copy of the 270 | written offer to provide the Corresponding Source. This 271 | alternative is allowed only occasionally and noncommercially, and 272 | only if you received the object code with such an offer, in accord 273 | with subsection 6b. 274 | 275 | d) Convey the object code by offering access from a designated 276 | place (gratis or for a charge), and offer equivalent access to the 277 | Corresponding Source in the same way through the same place at no 278 | further charge. You need not require recipients to copy the 279 | Corresponding Source along with the object code. If the place to 280 | copy the object code is a network server, the Corresponding Source 281 | may be on a different server (operated by you or a third party) 282 | that supports equivalent copying facilities, provided you maintain 283 | clear directions next to the object code saying where to find the 284 | Corresponding Source. Regardless of what server hosts the 285 | Corresponding Source, you remain obligated to ensure that it is 286 | available for as long as needed to satisfy these requirements. 287 | 288 | e) Convey the object code using peer-to-peer transmission, provided 289 | you inform other peers where the object code and Corresponding 290 | Source of the work are being offered to the general public at no 291 | charge under subsection 6d. 292 | 293 | A separable portion of the object code, whose source code is excluded 294 | from the Corresponding Source as a System Library, need not be 295 | included in conveying the object code work. 296 | 297 | A "User Product" is either (1) a "consumer product", which means any 298 | tangible personal property which is normally used for personal, family, 299 | or household purposes, or (2) anything designed or sold for incorporation 300 | into a dwelling. In determining whether a product is a consumer product, 301 | doubtful cases shall be resolved in favor of coverage. For a particular 302 | product received by a particular user, "normally used" refers to a 303 | typical or common use of that class of product, regardless of the status 304 | of the particular user or of the way in which the particular user 305 | actually uses, or expects or is expected to use, the product. A product 306 | is a consumer product regardless of whether the product has substantial 307 | commercial, industrial or non-consumer uses, unless such uses represent 308 | the only significant mode of use of the product. 309 | 310 | "Installation Information" for a User Product means any methods, 311 | procedures, authorization keys, or other information required to install 312 | and execute modified versions of a covered work in that User Product from 313 | a modified version of its Corresponding Source. The information must 314 | suffice to ensure that the continued functioning of the modified object 315 | code is in no case prevented or interfered with solely because 316 | modification has been made. 317 | 318 | If you convey an object code work under this section in, or with, or 319 | specifically for use in, a User Product, and the conveying occurs as 320 | part of a transaction in which the right of possession and use of the 321 | User Product is transferred to the recipient in perpetuity or for a 322 | fixed term (regardless of how the transaction is characterized), the 323 | Corresponding Source conveyed under this section must be accompanied 324 | by the Installation Information. But this requirement does not apply 325 | if neither you nor any third party retains the ability to install 326 | modified object code on the User Product (for example, the work has 327 | been installed in ROM). 328 | 329 | The requirement to provide Installation Information does not include a 330 | requirement to continue to provide support service, warranty, or updates 331 | for a work that has been modified or installed by the recipient, or for 332 | the User Product in which it has been modified or installed. Access to a 333 | network may be denied when the modification itself materially and 334 | adversely affects the operation of the network or violates the rules and 335 | protocols for communication across the network. 336 | 337 | Corresponding Source conveyed, and Installation Information provided, 338 | in accord with this section must be in a format that is publicly 339 | documented (and with an implementation available to the public in 340 | source code form), and must require no special password or key for 341 | unpacking, reading or copying. 342 | 343 | 7. Additional Terms. 344 | 345 | "Additional permissions" are terms that supplement the terms of this 346 | License by making exceptions from one or more of its conditions. 347 | Additional permissions that are applicable to the entire Program shall 348 | be treated as though they were included in this License, to the extent 349 | that they are valid under applicable law. If additional permissions 350 | apply only to part of the Program, that part may be used separately 351 | under those permissions, but the entire Program remains governed by 352 | this License without regard to the additional permissions. 353 | 354 | When you convey a copy of a covered work, you may at your option 355 | remove any additional permissions from that copy, or from any part of 356 | it. (Additional permissions may be written to require their own 357 | removal in certain cases when you modify the work.) You may place 358 | additional permissions on material, added by you to a covered work, 359 | for which you have or can give appropriate copyright permission. 360 | 361 | Notwithstanding any other provision of this License, for material you 362 | add to a covered work, you may (if authorized by the copyright holders of 363 | that material) supplement the terms of this License with terms: 364 | 365 | a) Disclaiming warranty or limiting liability differently from the 366 | terms of sections 15 and 16 of this License; or 367 | 368 | b) Requiring preservation of specified reasonable legal notices or 369 | author attributions in that material or in the Appropriate Legal 370 | Notices displayed by works containing it; or 371 | 372 | c) Prohibiting misrepresentation of the origin of that material, or 373 | requiring that modified versions of such material be marked in 374 | reasonable ways as different from the original version; or 375 | 376 | d) Limiting the use for publicity purposes of names of licensors or 377 | authors of the material; or 378 | 379 | e) Declining to grant rights under trademark law for use of some 380 | trade names, trademarks, or service marks; or 381 | 382 | f) Requiring indemnification of licensors and authors of that 383 | material by anyone who conveys the material (or modified versions of 384 | it) with contractual assumptions of liability to the recipient, for 385 | any liability that these contractual assumptions directly impose on 386 | those licensors and authors. 387 | 388 | All other non-permissive additional terms are considered "further 389 | restrictions" within the meaning of section 10. If the Program as you 390 | received it, or any part of it, contains a notice stating that it is 391 | governed by this License along with a term that is a further 392 | restriction, you may remove that term. If a license document contains 393 | a further restriction but permits relicensing or conveying under this 394 | License, you may add to a covered work material governed by the terms 395 | of that license document, provided that the further restriction does 396 | not survive such relicensing or conveying. 397 | 398 | If you add terms to a covered work in accord with this section, you 399 | must place, in the relevant source files, a statement of the 400 | additional terms that apply to those files, or a notice indicating 401 | where to find the applicable terms. 402 | 403 | Additional terms, permissive or non-permissive, may be stated in the 404 | form of a separately written license, or stated as exceptions; 405 | the above requirements apply either way. 406 | 407 | 8. Termination. 408 | 409 | You may not propagate or modify a covered work except as expressly 410 | provided under this License. Any attempt otherwise to propagate or 411 | modify it is void, and will automatically terminate your rights under 412 | this License (including any patent licenses granted under the third 413 | paragraph of section 11). 414 | 415 | However, if you cease all violation of this License, then your 416 | license from a particular copyright holder is reinstated (a) 417 | provisionally, unless and until the copyright holder explicitly and 418 | finally terminates your license, and (b) permanently, if the copyright 419 | holder fails to notify you of the violation by some reasonable means 420 | prior to 60 days after the cessation. 421 | 422 | Moreover, your license from a particular copyright holder is 423 | reinstated permanently if the copyright holder notifies you of the 424 | violation by some reasonable means, this is the first time you have 425 | received notice of violation of this License (for any work) from that 426 | copyright holder, and you cure the violation prior to 30 days after 427 | your receipt of the notice. 428 | 429 | Termination of your rights under this section does not terminate the 430 | licenses of parties who have received copies or rights from you under 431 | this License. If your rights have been terminated and not permanently 432 | reinstated, you do not qualify to receive new licenses for the same 433 | material under section 10. 434 | 435 | 9. Acceptance Not Required for Having Copies. 436 | 437 | You are not required to accept this License in order to receive or 438 | run a copy of the Program. Ancillary propagation of a covered work 439 | occurring solely as a consequence of using peer-to-peer transmission 440 | to receive a copy likewise does not require acceptance. However, 441 | nothing other than this License grants you permission to propagate or 442 | modify any covered work. These actions infringe copyright if you do 443 | not accept this License. Therefore, by modifying or propagating a 444 | covered work, you indicate your acceptance of this License to do so. 445 | 446 | 10. Automatic Licensing of Downstream Recipients. 447 | 448 | Each time you convey a covered work, the recipient automatically 449 | receives a license from the original licensors, to run, modify and 450 | propagate that work, subject to this License. You are not responsible 451 | for enforcing compliance by third parties with this License. 452 | 453 | An "entity transaction" is a transaction transferring control of an 454 | organization, or substantially all assets of one, or subdividing an 455 | organization, or merging organizations. If propagation of a covered 456 | work results from an entity transaction, each party to that 457 | transaction who receives a copy of the work also receives whatever 458 | licenses to the work the party's predecessor in interest had or could 459 | give under the previous paragraph, plus a right to possession of the 460 | Corresponding Source of the work from the predecessor in interest, if 461 | the predecessor has it or can get it with reasonable efforts. 462 | 463 | You may not impose any further restrictions on the exercise of the 464 | rights granted or affirmed under this License. For example, you may 465 | not impose a license fee, royalty, or other charge for exercise of 466 | rights granted under this License, and you may not initiate litigation 467 | (including a cross-claim or counterclaim in a lawsuit) alleging that 468 | any patent claim is infringed by making, using, selling, offering for 469 | sale, or importing the Program or any portion of it. 470 | 471 | 11. Patents. 472 | 473 | A "contributor" is a copyright holder who authorizes use under this 474 | License of the Program or a work on which the Program is based. The 475 | work thus licensed is called the contributor's "contributor version". 476 | 477 | A contributor's "essential patent claims" are all patent claims 478 | owned or controlled by the contributor, whether already acquired or 479 | hereafter acquired, that would be infringed by some manner, permitted 480 | by this License, of making, using, or selling its contributor version, 481 | but do not include claims that would be infringed only as a 482 | consequence of further modification of the contributor version. For 483 | purposes of this definition, "control" includes the right to grant 484 | patent sublicenses in a manner consistent with the requirements of 485 | this License. 486 | 487 | Each contributor grants you a non-exclusive, worldwide, royalty-free 488 | patent license under the contributor's essential patent claims, to 489 | make, use, sell, offer for sale, import and otherwise run, modify and 490 | propagate the contents of its contributor version. 491 | 492 | In the following three paragraphs, a "patent license" is any express 493 | agreement or commitment, however denominated, not to enforce a patent 494 | (such as an express permission to practice a patent or covenant not to 495 | sue for patent infringement). To "grant" such a patent license to a 496 | party means to make such an agreement or commitment not to enforce a 497 | patent against the party. 498 | 499 | If you convey a covered work, knowingly relying on a patent license, 500 | and the Corresponding Source of the work is not available for anyone 501 | to copy, free of charge and under the terms of this License, through a 502 | publicly available network server or other readily accessible means, 503 | then you must either (1) cause the Corresponding Source to be so 504 | available, or (2) arrange to deprive yourself of the benefit of the 505 | patent license for this particular work, or (3) arrange, in a manner 506 | consistent with the requirements of this License, to extend the patent 507 | license to downstream recipients. "Knowingly relying" means you have 508 | actual knowledge that, but for the patent license, your conveying the 509 | covered work in a country, or your recipient's use of the covered work 510 | in a country, would infringe one or more identifiable patents in that 511 | country that you have reason to believe are valid. 512 | 513 | If, pursuant to or in connection with a single transaction or 514 | arrangement, you convey, or propagate by procuring conveyance of, a 515 | covered work, and grant a patent license to some of the parties 516 | receiving the covered work authorizing them to use, propagate, modify 517 | or convey a specific copy of the covered work, then the patent license 518 | you grant is automatically extended to all recipients of the covered 519 | work and works based on it. 520 | 521 | A patent license is "discriminatory" if it does not include within 522 | the scope of its coverage, prohibits the exercise of, or is 523 | conditioned on the non-exercise of one or more of the rights that are 524 | specifically granted under this License. You may not convey a covered 525 | work if you are a party to an arrangement with a third party that is 526 | in the business of distributing software, under which you make payment 527 | to the third party based on the extent of your activity of conveying 528 | the work, and under which the third party grants, to any of the 529 | parties who would receive the covered work from you, a discriminatory 530 | patent license (a) in connection with copies of the covered work 531 | conveyed by you (or copies made from those copies), or (b) primarily 532 | for and in connection with specific products or compilations that 533 | contain the covered work, unless you entered into that arrangement, 534 | or that patent license was granted, prior to 28 March 2007. 535 | 536 | Nothing in this License shall be construed as excluding or limiting 537 | any implied license or other defenses to infringement that may 538 | otherwise be available to you under applicable patent law. 539 | 540 | 12. No Surrender of Others' Freedom. 541 | 542 | If conditions are imposed on you (whether by court order, agreement or 543 | otherwise) that contradict the conditions of this License, they do not 544 | excuse you from the conditions of this License. If you cannot convey a 545 | covered work so as to satisfy simultaneously your obligations under this 546 | License and any other pertinent obligations, then as a consequence you may 547 | not convey it at all. For example, if you agree to terms that obligate you 548 | to collect a royalty for further conveying from those to whom you convey 549 | the Program, the only way you could satisfy both those terms and this 550 | License would be to refrain entirely from conveying the Program. 551 | 552 | 13. Use with the GNU Affero General Public License. 553 | 554 | Notwithstanding any other provision of this License, you have 555 | permission to link or combine any covered work with a work licensed 556 | under version 3 of the GNU Affero General Public License into a single 557 | combined work, and to convey the resulting work. The terms of this 558 | License will continue to apply to the part which is the covered work, 559 | but the special requirements of the GNU Affero General Public License, 560 | section 13, concerning interaction through a network will apply to the 561 | combination as such. 562 | 563 | 14. Revised Versions of this License. 564 | 565 | The Free Software Foundation may publish revised and/or new versions of 566 | the GNU General Public License from time to time. Such new versions will 567 | be similar in spirit to the present version, but may differ in detail to 568 | address new problems or concerns. 569 | 570 | Each version is given a distinguishing version number. If the 571 | Program specifies that a certain numbered version of the GNU General 572 | Public License "or any later version" applies to it, you have the 573 | option of following the terms and conditions either of that numbered 574 | version or of any later version published by the Free Software 575 | Foundation. If the Program does not specify a version number of the 576 | GNU General Public License, you may choose any version ever published 577 | by the Free Software Foundation. 578 | 579 | If the Program specifies that a proxy can decide which future 580 | versions of the GNU General Public License can be used, that proxy's 581 | public statement of acceptance of a version permanently authorizes you 582 | to choose that version for the Program. 583 | 584 | Later license versions may give you additional or different 585 | permissions. However, no additional obligations are imposed on any 586 | author or copyright holder as a result of your choosing to follow a 587 | later version. 588 | 589 | 15. Disclaimer of Warranty. 590 | 591 | THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY 592 | APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT 593 | HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY 594 | OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, 595 | THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR 596 | PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM 597 | IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF 598 | ALL NECESSARY SERVICING, REPAIR OR CORRECTION. 599 | 600 | 16. Limitation of Liability. 601 | 602 | IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING 603 | WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS 604 | THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY 605 | GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE 606 | USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF 607 | DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD 608 | PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), 609 | EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF 610 | SUCH DAMAGES. 611 | 612 | 17. Interpretation of Sections 15 and 16. 613 | 614 | If the disclaimer of warranty and limitation of liability provided 615 | above cannot be given local legal effect according to their terms, 616 | reviewing courts shall apply local law that most closely approximates 617 | an absolute waiver of all civil liability in connection with the 618 | Program, unless a warranty or assumption of liability accompanies a 619 | copy of the Program in return for a fee. 620 | 621 | END OF TERMS AND CONDITIONS 622 | 623 | How to Apply These Terms to Your New Programs 624 | 625 | If you develop a new program, and you want it to be of the greatest 626 | possible use to the public, the best way to achieve this is to make it 627 | free software which everyone can redistribute and change under these terms. 628 | 629 | To do so, attach the following notices to the program. It is safest 630 | to attach them to the start of each source file to most effectively 631 | state the exclusion of warranty; and each file should have at least 632 | the "copyright" line and a pointer to where the full notice is found. 633 | 634 | 635 | Copyright (C) 636 | 637 | This program is free software: you can redistribute it and/or modify 638 | it under the terms of the GNU General Public License as published by 639 | the Free Software Foundation, either version 3 of the License, or 640 | (at your option) any later version. 641 | 642 | This program is distributed in the hope that it will be useful, 643 | but WITHOUT ANY WARRANTY; without even the implied warranty of 644 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 645 | GNU General Public License for more details. 646 | 647 | You should have received a copy of the GNU General Public License 648 | along with this program. If not, see . 649 | 650 | Also add information on how to contact you by electronic and paper mail. 651 | 652 | If the program does terminal interaction, make it output a short 653 | notice like this when it starts in an interactive mode: 654 | 655 | Copyright (C) 656 | This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'. 657 | This is free software, and you are welcome to redistribute it 658 | under certain conditions; type `show c' for details. 659 | 660 | The hypothetical commands `show w' and `show c' should show the appropriate 661 | parts of the General Public License. Of course, your program's commands 662 | might be different; for a GUI interface, you would use an "about box". 663 | 664 | You should also get your employer (if you work as a programmer) or school, 665 | if any, to sign a "copyright disclaimer" for the program, if necessary. 666 | For more information on this, and how to apply and follow the GNU GPL, see 667 | . 668 | 669 | The GNU General Public License does not permit incorporating your program 670 | into proprietary programs. If your program is a subroutine library, you 671 | may consider it more useful to permit linking proprietary applications with 672 | the library. If this is what you want to do, use the GNU Lesser General 673 | Public License instead of this License. But first, please read 674 | . 675 | 676 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | .PHONY:build 2 | 3 | build: 4 | go build -ldflags="-w -s" 5 | 6 | install: 7 | mv -v dotsync /usr/bin 8 | 9 | install_remote: 10 | go install github.com/mbaraa/dotsync@latest 11 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Dotsync 2 | 3 | [![GoDoc](https://godoc.org/github.com/mbaraa/dotsync?status.png)](https://godoc.org/github.com/mbaraa/dotsync) 4 | . 5 | [![build](https://github.com/mbaraa/dotsync/actions/workflows/build.yml/badge.svg)](https://github.com/mbaraa/dotsync/actions/workflows/build.yml) 6 | . 7 | [![Go Report Card](https://goreportcard.com/badge/github.com/mbaraa/dotsync)](https://goreportcard.com/report/github.com/mbaraa/dotsync) 8 | . 9 | [![server-rex-deploy](https://github.com/mbaraa/dotsync/actions/workflows/server-rex-deploy.yml/badge.svg)](https://github.com/mbaraa/dotsync/actions/workflows/server-rex-deploy.yml) 10 | . 11 | [![website-rex-deploy](https://github.com/mbaraa/dotsync/actions/workflows/website-rex-deploy.yml/badge.svg)](https://github.com/mbaraa/dotsync/actions/workflows/website-rex-deploy.yml) 12 | 13 | A small, free, open-source, blazingly fast dotfiles synchronizer! 14 | 15 | Dotsync's [server](/server) is the middleware between your connected computers, where all of your dotfiles stand there encrypted, and backed up! 16 | 17 | ## Features: 18 | 19 | - Efficient 20 | - Lightweight 21 | - Blazingly Fast 22 | - Cool Stack 23 | - Open-source 24 | - Free (of charge & evil data telemetry things) 25 | - Self-hosting option, check [the server](/server) 26 | 27 | ## Dependencies: 28 | 29 | - [go](https://golang.org) 30 | - An internet connection 31 | - Linux or Unix-like system (I haven't tried it on Windows, a feedback is more than welcome) 32 | - A bunch of dotfiles to sync :) 33 | 34 | ## Installation: 35 | 36 | 48 | 49 | ### Using Go's installer 50 | 51 | ```bash 52 | go install github.com/mbaraa/dotsync@latest 53 | 54 | # or 55 | 56 | make 57 | sudo make install 58 | ``` 59 | 60 | ## Usage: 61 | 62 | ### Create/Login using an email 63 | 64 | And as mentioned above your email is encrypted, and won't be shared with anyone! 65 | 66 | 1. Login 67 | 68 | ```bash 69 | dotsync -login someone@example.com 70 | ``` 71 | 72 | 2. Enter the token which you recived as an email(it might arrive as a spam email) 73 | 74 | 3. Go nuts 75 | 76 | ### Sync 77 | 78 | 1. Add and upload a bunch of files 79 | 80 | ```bash 81 | # add a file 82 | dotsync -add ~/.bashrc 83 | dotsync -add ~/.config/i3/config 84 | dotsync -add ~/.config/nvim/ 85 | 86 | # upload your current files 87 | dotsync -upload 88 | ``` 89 | 90 | 2. Download your files on another computer to show the power of Dotsync 91 | 92 | ```bash 93 | # you need to login first, so... 94 | dotsync -download 95 | ``` 96 | 97 | **For a more detailed usage, visit the [Officical Docs](https://dotsync.org/docs)**! 98 | -------------------------------------------------------------------------------- /actions/action.go: -------------------------------------------------------------------------------- 1 | package actions 2 | 3 | import "io" 4 | 5 | type IAction interface { 6 | // Exec executes the selected action, and prints action's output on the given io.Writer 7 | // or exits with an error 8 | Exec(output io.Writer, args ...any) error 9 | 10 | // NeedsRoot return true if the action needs root to be executed 11 | NeedsRoot() bool 12 | 13 | // HasArgs returns true if the action requires arguments to run 14 | HasArgs() bool 15 | } 16 | 17 | type ActionType string 18 | 19 | const ( 20 | LoginActionType ActionType = "-login" 21 | DeleteUserActionType ActionType = "-delete-user" 22 | AddFileActionType ActionType = "-add" 23 | RemoveFileActionType ActionType = "-remove" 24 | ListFilesActionType ActionType = "-list" 25 | DownloadFilesActionType ActionType = "-download" 26 | UploadFilesActionType ActionType = "-upload" 27 | ) 28 | 29 | func GetAction(at ActionType) IAction { 30 | switch at { 31 | case LoginActionType: 32 | return NewLoginAction() 33 | case DeleteUserActionType: 34 | return NewDeleteUserAction() 35 | case AddFileActionType: 36 | return NewAddFileAction() 37 | case RemoveFileActionType: 38 | return NewRemoveFileAction() 39 | case ListFilesActionType: 40 | return NewListFilesAction() 41 | case DownloadFilesActionType: 42 | return NewDownloadFilesAction() 43 | case UploadFilesActionType: 44 | return NewUploadFilesAction() 45 | default: 46 | return nil 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /actions/add_file_action.go: -------------------------------------------------------------------------------- 1 | package actions 2 | 3 | import ( 4 | "bytes" 5 | "encoding/base64" 6 | "errors" 7 | "fmt" 8 | "io" 9 | "io/fs" 10 | "net/http" 11 | "os" 12 | "path/filepath" 13 | 14 | "github.com/mbaraa/dotsync/config" 15 | "github.com/mbaraa/dotsync/utils/configfile" 16 | "github.com/mbaraa/dotsync/utils/json" 17 | ) 18 | 19 | type AddFileAction struct { 20 | output io.Writer 21 | } 22 | 23 | func NewAddFileAction() IAction { 24 | return &AddFileAction{} 25 | } 26 | 27 | func (a *AddFileAction) Exec(output io.Writer, args ...any) error { 28 | a.output = output 29 | 30 | err := a.addFile(args[0].(string)) 31 | if err != nil { 32 | return err 33 | } 34 | 35 | return nil 36 | } 37 | 38 | func (a *AddFileAction) NeedsRoot() bool { 39 | return false 40 | } 41 | 42 | func (a *AddFileAction) HasArgs() bool { 43 | return true 44 | } 45 | 46 | func (a *AddFileAction) addFile(filePath string) error { 47 | file, err := os.Open(filePath) 48 | if err != nil { 49 | return err 50 | } 51 | 52 | stat, err := file.Stat() 53 | if err != nil { 54 | return err 55 | } 56 | 57 | if stat.IsDir() { 58 | return a.addDirectory(filePath) 59 | } 60 | 61 | if stat.Size() > 256*1024 { 62 | return errors.New("file is larger than 256KiB...") 63 | } 64 | 65 | fmt.Fprintf(a.output, "Adding %s to your synced files...\n", filePath) 66 | 67 | content, err := io.ReadAll(file) 68 | if err != nil { 69 | return err 70 | } 71 | 72 | filePath, err = filepath.Abs(filePath) 73 | if err != nil { 74 | return err 75 | } 76 | 77 | encContent := base64.StdEncoding.EncodeToString(content) 78 | 79 | reqBody := bytes.NewBuffer([]byte{}) 80 | _ = json.StringifyToWriter(reqBody, map[string]string{ 81 | "path": filePath, 82 | "content": encContent, 83 | }) 84 | 85 | req, err := http.NewRequest("POST", config.ServerAddress+"/file", reqBody) 86 | req.Header.Add("Content-Type", "application/json") 87 | token, err := configfile.GetValue("token") 88 | if err != nil { 89 | return err 90 | } 91 | req.Header.Add("Authorization", token) 92 | if err != nil { 93 | return err 94 | } 95 | resp, err := http.DefaultClient.Do(req) 96 | if err != nil { 97 | return err 98 | } 99 | 100 | if resp.StatusCode != 200 { 101 | respBody, _ := json.ParseFromReader[json.Json](resp.Body) 102 | resp.Body.Close() 103 | return errors.New(respBody["error"].(string)) 104 | } 105 | 106 | fmt.Fprintln(a.output, "Done, you can check the synced files list by using `dotsync -list`") 107 | 108 | return nil 109 | } 110 | 111 | func (a *AddFileAction) addDirectory(dirPath string) error { 112 | uploadList := make([]struct { 113 | Path string `json:"path"` 114 | Content string `json:"content"` 115 | }, 0) 116 | 117 | fmt.Fprintf(a.output, "reading files inside %s...\n", dirPath) 118 | err := filepath.Walk(dirPath, func(path string, info fs.FileInfo, err error) error { 119 | if info.IsDir() { 120 | return nil 121 | } 122 | if info.Size() > 256*1024 { 123 | return errors.New(fmt.Sprintf("the file %s is larger than 256KiB...", path)) 124 | } 125 | 126 | content, err := os.ReadFile(path) 127 | if err != nil { 128 | return err 129 | } 130 | uploadList = append(uploadList, struct { 131 | Path string `json:"path"` 132 | Content string `json:"content"` 133 | }{ 134 | Path: path, 135 | Content: string(content), 136 | }) 137 | 138 | return nil 139 | }) 140 | if err != nil { 141 | return err 142 | } 143 | fmt.Fprintln(a.output, "done 👍") 144 | 145 | fmt.Fprintln(a.output, "uploading your new files...") 146 | reqBody := bytes.NewBuffer([]byte{}) 147 | _ = json.StringifyToWriter(reqBody, map[string]any{ 148 | "files": uploadList, 149 | }) 150 | 151 | req, err := http.NewRequest("POST", config.ServerAddress+"/file/add-directory", reqBody) 152 | req.Header.Add("Content-Type", "application/json") 153 | token, err := configfile.GetValue("token") 154 | if err != nil { 155 | return err 156 | } 157 | req.Header.Add("Authorization", token) 158 | if err != nil { 159 | return err 160 | } 161 | resp, err := http.DefaultClient.Do(req) 162 | if err != nil { 163 | return err 164 | } 165 | 166 | if resp.StatusCode != 200 { 167 | respBody, _ := json.ParseFromReader[json.Json](resp.Body) 168 | resp.Body.Close() 169 | return errors.New(respBody["error"].(string)) 170 | } 171 | fmt.Fprintln(a.output, "done 👍") 172 | 173 | return nil 174 | } 175 | -------------------------------------------------------------------------------- /actions/delete_user_action.go: -------------------------------------------------------------------------------- 1 | package actions 2 | 3 | import ( 4 | "errors" 5 | "fmt" 6 | "io" 7 | "net/http" 8 | "strings" 9 | 10 | "github.com/mbaraa/dotsync/config" 11 | "github.com/mbaraa/dotsync/utils/configfile" 12 | "github.com/mbaraa/dotsync/utils/json" 13 | ) 14 | 15 | type DeleteUserAction struct { 16 | output io.Writer 17 | } 18 | 19 | func NewDeleteUserAction() IAction { 20 | return &DeleteUserAction{} 21 | } 22 | 23 | func (d *DeleteUserAction) Exec(output io.Writer, args ...any) error { 24 | d.output = output 25 | 26 | err := d.deleteUser() 27 | if err != nil { 28 | return err 29 | } 30 | 31 | return nil 32 | } 33 | 34 | func (d *DeleteUserAction) NeedsRoot() bool { 35 | return false 36 | } 37 | 38 | func (d *DeleteUserAction) HasArgs() bool { 39 | return false 40 | } 41 | 42 | func (d *DeleteUserAction) deleteUser() error { 43 | fmt.Fprint(d.output, "Are you sure that you want to delete your user and remote file? [y|N] ") 44 | var choice string 45 | fmt.Scanln(&choice) 46 | switch strings.ToLower(choice) { 47 | case "Y", "y", "yes": 48 | break 49 | case "N", "n", "no": 50 | return errors.New("user canceled") 51 | default: 52 | return errors.New("invalid choice") 53 | } 54 | 55 | fmt.Fprintln(d.output, "deleting your user...") 56 | req, err := http.NewRequest("DELETE", config.ServerAddress+"/user", nil) 57 | token, err := configfile.GetValue("token") 58 | if err != nil { 59 | return err 60 | } 61 | req.Header.Add("Authorization", token) 62 | if err != nil { 63 | return err 64 | } 65 | resp, err := http.DefaultClient.Do(req) 66 | if err != nil { 67 | return err 68 | } 69 | 70 | if resp.StatusCode != 200 { 71 | respBody, _ := json.ParseFromReader[json.Json](resp.Body) 72 | resp.Body.Close() 73 | return errors.New(respBody["error"].(string)) 74 | } 75 | 76 | fmt.Fprintln(d.output, "Sad to see you go...") 77 | fmt.Fprintln(d.output, "If there's anything that bothered you in Dotsync feel free to contact me at dotsync@mbaraa.com") 78 | 79 | return nil 80 | } 81 | -------------------------------------------------------------------------------- /actions/download_files_action.go: -------------------------------------------------------------------------------- 1 | package actions 2 | 3 | import ( 4 | "encoding/base64" 5 | "errors" 6 | "fmt" 7 | "io" 8 | "net/http" 9 | "os" 10 | 11 | "github.com/mbaraa/dotsync/config" 12 | "github.com/mbaraa/dotsync/utils/configfile" 13 | "github.com/mbaraa/dotsync/utils/json" 14 | ) 15 | 16 | type DownloadFilesAction struct { 17 | output io.Writer 18 | } 19 | 20 | func NewDownloadFilesAction() IAction { 21 | return &DownloadFilesAction{} 22 | } 23 | 24 | func (d *DownloadFilesAction) Exec(output io.Writer, args ...any) error { 25 | d.output = output 26 | 27 | err := d.downloadFiles() 28 | if err != nil { 29 | return err 30 | } 31 | 32 | return nil 33 | } 34 | 35 | func (d *DownloadFilesAction) NeedsRoot() bool { 36 | return false 37 | } 38 | 39 | func (d *DownloadFilesAction) HasArgs() bool { 40 | return false 41 | } 42 | 43 | func (d *DownloadFilesAction) downloadFiles() error { 44 | fmt.Fprintln(d.output, "downloading your dotfiles...") 45 | req, err := http.NewRequest("GET", config.ServerAddress+"/file/download", nil) 46 | token, err := configfile.GetValue("token") 47 | if err != nil { 48 | return err 49 | } 50 | req.Header.Add("Authorization", token) 51 | if err != nil { 52 | return err 53 | } 54 | resp, err := http.DefaultClient.Do(req) 55 | if err != nil { 56 | return err 57 | } 58 | 59 | if resp.StatusCode != 200 { 60 | respBody, _ := json.ParseFromReader[json.Json](resp.Body) 61 | resp.Body.Close() 62 | return errors.New(respBody["error"].(string)) 63 | } 64 | 65 | files, err := json.ParseFromReader[[]struct { 66 | Path string `json:"path"` 67 | Content string `json:"content"` 68 | }](resp.Body) 69 | if err != nil { 70 | return err 71 | } 72 | resp.Body.Close() 73 | fmt.Fprintln(d.output, "done 👍") 74 | 75 | fmt.Fprintln(d.output, "\nupdating your local dotfiles...") 76 | for _, file := range files { 77 | fmt.Fprintf(d.output, "saving file %s...", file.Path) 78 | 79 | err = os.Truncate(file.Path, 0) 80 | if err != nil && !os.IsNotExist(err) { 81 | fmt.Fprintln(d.output, err.Error()) 82 | continue 83 | } 84 | 85 | f, err := os.OpenFile(file.Path, os.O_CREATE|os.O_RDWR, 0755) 86 | if err != nil { 87 | fmt.Fprintf(d.output, "opening file %s failed, reason: %s", file.Path, err.Error()) 88 | continue 89 | } 90 | 91 | decContent, _ := base64.StdEncoding.DecodeString(file.Content) 92 | _, err = f.Write(decContent) 93 | if err != nil { 94 | fmt.Fprintf(d.output, "saving file %s failed, reason: %s", file.Path, err.Error()) 95 | continue 96 | } 97 | fmt.Fprintln(d.output, "Done 👍") 98 | } 99 | fmt.Fprintln(d.output, "All is done 👍") 100 | fmt.Fprintln(d.output, "Some of your programs needs a restart after the download, just saying 😁") 101 | 102 | return nil 103 | } 104 | -------------------------------------------------------------------------------- /actions/list_files_action.go: -------------------------------------------------------------------------------- 1 | package actions 2 | 3 | import ( 4 | "errors" 5 | "fmt" 6 | "io" 7 | "net/http" 8 | 9 | "github.com/mbaraa/dotsync/config" 10 | "github.com/mbaraa/dotsync/utils/configfile" 11 | "github.com/mbaraa/dotsync/utils/json" 12 | ) 13 | 14 | type ListFilesAction struct { 15 | output io.Writer 16 | } 17 | 18 | func NewListFilesAction() IAction { 19 | return &ListFilesAction{} 20 | } 21 | 22 | func (l *ListFilesAction) Exec(output io.Writer, args ...any) error { 23 | l.output = output 24 | 25 | err := l.listFiles() 26 | if err != nil { 27 | return err 28 | } 29 | 30 | return nil 31 | } 32 | 33 | func (l *ListFilesAction) NeedsRoot() bool { 34 | return false 35 | } 36 | 37 | func (l *ListFilesAction) HasArgs() bool { 38 | return false 39 | } 40 | 41 | func (l *ListFilesAction) listFiles() error { 42 | req, err := http.NewRequest("GET", config.ServerAddress+"/file", nil) 43 | token, err := configfile.GetValue("token") 44 | if err != nil { 45 | return err 46 | } 47 | req.Header.Add("Authorization", token) 48 | if err != nil { 49 | return err 50 | } 51 | resp, err := http.DefaultClient.Do(req) 52 | if err != nil { 53 | return err 54 | } 55 | 56 | if resp.StatusCode != 200 { 57 | respBody, _ := json.ParseFromReader[json.Json](resp.Body) 58 | resp.Body.Close() 59 | return errors.New(respBody["error"].(string)) 60 | } 61 | 62 | files, err := json.ParseFromReader[[]string](resp.Body) 63 | if err != nil { 64 | return err 65 | } 66 | resp.Body.Close() 67 | 68 | fmt.Fprintln(l.output, "Your current synced files:") 69 | for i, filePath := range files { 70 | fmt.Fprintf(l.output, "%d) %s\n", i+1, filePath) 71 | } 72 | 73 | return nil 74 | } 75 | -------------------------------------------------------------------------------- /actions/login_action.go: -------------------------------------------------------------------------------- 1 | package actions 2 | 3 | import ( 4 | "bytes" 5 | "errors" 6 | "fmt" 7 | "io" 8 | "net/http" 9 | "strings" 10 | "time" 11 | 12 | "github.com/mbaraa/dotsync/config" 13 | "github.com/mbaraa/dotsync/utils/configfile" 14 | "github.com/mbaraa/dotsync/utils/json" 15 | ) 16 | 17 | type LoginAction struct { 18 | output io.Writer 19 | } 20 | 21 | func NewLoginAction() IAction { 22 | return &LoginAction{} 23 | } 24 | 25 | func (l *LoginAction) Exec(output io.Writer, args ...any) error { 26 | l.output = output 27 | 28 | err := l.login(args[0].(string)) 29 | if err != nil { 30 | return err 31 | } 32 | 33 | return nil 34 | } 35 | 36 | func (l *LoginAction) NeedsRoot() bool { 37 | return false 38 | } 39 | 40 | func (l *LoginAction) HasArgs() bool { 41 | return true 42 | } 43 | 44 | func (l *LoginAction) login(email string) error { 45 | fmt.Fprintf(l.output, "Sending an email with the login token to %s.\n", email) 46 | 47 | reqBody := bytes.NewBuffer([]byte{}) 48 | _ = json.StringifyToWriter(reqBody, map[string]string{ 49 | "email": email, 50 | }) 51 | resp, err := http.Post(config.ServerAddress+"/user/login", "application/json", reqBody) 52 | if err != nil { 53 | return err 54 | } 55 | 56 | if resp.StatusCode == 400 { 57 | return errors.New("invalid email address") 58 | } 59 | if resp.StatusCode != 200 { 60 | return errors.New("something went wrong...") 61 | } 62 | 63 | respBody, err := json.ParseFromReader[json.Json](resp.Body) 64 | if err != nil { 65 | return err 66 | } 67 | 68 | var token string 69 | if tok, ok := respBody["token"].(string); ok { 70 | token = tok 71 | } 72 | 73 | fmt.Fprintf(l.output, "An email was sent to %s, with the login token.\n", email) 74 | fmt.Fprintln(l.output, "Copy the token and paste it here to complete the login process.") 75 | 76 | return l.storeToken(token) 77 | } 78 | 79 | func (l *LoginAction) storeToken(midPart string) error { 80 | fmt.Fprint(l.output, "\nEnter the login token: ") 81 | var loginToken string 82 | fmt.Scanln(&loginToken) 83 | 84 | tokenParts := strings.Split(loginToken, "🔒") 85 | if len(tokenParts) != 2 { 86 | return errors.New("invalid token") 87 | } 88 | loginToken = fmt.Sprintf("%s.%s.%s", tokenParts[0], midPart, tokenParts[1]) 89 | 90 | fmt.Fprintln(l.output, "\nChecking your token, hope you're using your token") 91 | reqBody := bytes.NewBuffer([]byte{}) 92 | _ = json.StringifyToWriter(reqBody, map[string]string{ 93 | "token": loginToken, 94 | }) 95 | resp, err := http.Post(config.ServerAddress+"/user/login/verify", "application/json", reqBody) 96 | if err != nil { 97 | return err 98 | } 99 | 100 | time.Sleep(time.Second) 101 | 102 | if resp.StatusCode == 401 { 103 | return errors.New("invalid or expired token") 104 | } 105 | if resp.StatusCode != 200 { 106 | return errors.New("something went wrong...") 107 | } 108 | 109 | fmt.Fprintln(l.output, "seems legit, carry on...") 110 | fmt.Fprintln(l.output, "check -help, or the official docs at https://dotsync.org/docs, for a detailed usage!") 111 | 112 | respBody, err := json.ParseFromReader[json.Json](resp.Body) 113 | if err != nil { 114 | return err 115 | } 116 | 117 | var token string 118 | if tok, ok := respBody["token"].(string); ok { 119 | token = tok 120 | } 121 | 122 | err = configfile.SetValue("token", token) 123 | if err != nil { 124 | return err 125 | } 126 | 127 | return nil 128 | } 129 | -------------------------------------------------------------------------------- /actions/remove_file_action.go: -------------------------------------------------------------------------------- 1 | package actions 2 | 3 | import ( 4 | "errors" 5 | "fmt" 6 | "io" 7 | "net/http" 8 | "strings" 9 | 10 | "github.com/mbaraa/dotsync/config" 11 | "github.com/mbaraa/dotsync/utils/configfile" 12 | "github.com/mbaraa/dotsync/utils/json" 13 | ) 14 | 15 | type RemoveFileAction struct { 16 | output io.Writer 17 | } 18 | 19 | func NewRemoveFileAction() IAction { 20 | return &RemoveFileAction{} 21 | } 22 | 23 | func (r *RemoveFileAction) Exec(output io.Writer, args ...any) error { 24 | r.output = output 25 | 26 | err := r.removeFile(args[0].(string)) 27 | if err != nil { 28 | return err 29 | } 30 | 31 | return nil 32 | } 33 | 34 | func (r *RemoveFileAction) NeedsRoot() bool { 35 | return false 36 | } 37 | 38 | func (r *RemoveFileAction) HasArgs() bool { 39 | return true 40 | } 41 | 42 | func (r *RemoveFileAction) removeFile(filePath string) error { 43 | fmt.Fprintf(r.output, "Remove '%s' from your synced files? [y|N] ", filePath) 44 | var choice string 45 | fmt.Scanln(&choice) 46 | switch strings.ToLower(choice) { 47 | case "Y", "y", "yes": 48 | break 49 | case "N", "n", "no": 50 | return errors.New("user canceled") 51 | default: 52 | return errors.New("invalid choice") 53 | } 54 | 55 | req, err := http.NewRequest("DELETE", fmt.Sprintf("%s/file?path=%s", config.ServerAddress, filePath), nil) 56 | token, err := configfile.GetValue("token") 57 | if err != nil { 58 | return err 59 | } 60 | req.Header.Add("Authorization", token) 61 | if err != nil { 62 | return err 63 | } 64 | resp, err := http.DefaultClient.Do(req) 65 | if err != nil { 66 | return err 67 | } 68 | 69 | if resp.StatusCode != 200 { 70 | respBody, _ := json.ParseFromReader[json.Json](resp.Body) 71 | resp.Body.Close() 72 | return errors.New(respBody["error"].(string)) 73 | } 74 | 75 | fmt.Fprintln(r.output, "Done, you can check the synced files list by using `dotsync -list`") 76 | 77 | return nil 78 | } 79 | -------------------------------------------------------------------------------- /actions/upload_files_action.go: -------------------------------------------------------------------------------- 1 | package actions 2 | 3 | import ( 4 | "bytes" 5 | "encoding/base64" 6 | "errors" 7 | "fmt" 8 | "io" 9 | "net/http" 10 | "os" 11 | 12 | "github.com/mbaraa/dotsync/config" 13 | "github.com/mbaraa/dotsync/utils/configfile" 14 | "github.com/mbaraa/dotsync/utils/json" 15 | ) 16 | 17 | type UploadFilesAction struct { 18 | output io.Writer 19 | } 20 | 21 | func NewUploadFilesAction() IAction { 22 | return &UploadFilesAction{} 23 | } 24 | 25 | func (u *UploadFilesAction) Exec(output io.Writer, args ...any) error { 26 | u.output = output 27 | 28 | err := u.uploadFiles() 29 | if err != nil { 30 | return err 31 | } 32 | 33 | return nil 34 | } 35 | 36 | func (u *UploadFilesAction) NeedsRoot() bool { 37 | return false 38 | } 39 | 40 | func (u *UploadFilesAction) HasArgs() bool { 41 | return false 42 | } 43 | 44 | func (u *UploadFilesAction) uploadFiles() error { 45 | files, err := u.listFiles() 46 | if err != nil { 47 | return err 48 | } 49 | 50 | updateList := make([]struct { 51 | Path string `json:"path"` 52 | Content string `json:"content"` 53 | }, 0) 54 | 55 | for _, filePath := range files { 56 | file, err := os.Open(filePath) 57 | if err != nil { 58 | return err 59 | } 60 | 61 | stat, err := file.Stat() 62 | if err != nil { 63 | return err 64 | } 65 | 66 | if stat.Size() > 256*1024 { 67 | return errors.New(fmt.Sprintf("file %s is larger than 256KiB...", filePath)) 68 | } 69 | content, err := io.ReadAll(file) 70 | if err != nil { 71 | return err 72 | } 73 | 74 | encContent := base64.StdEncoding.EncodeToString(content) 75 | updateList = append(updateList, struct { 76 | Path string `json:"path"` 77 | Content string `json:"content"` 78 | }{ 79 | Path: filePath, 80 | Content: encContent, 81 | }) 82 | 83 | } 84 | 85 | fmt.Fprintln(u.output, "updating your synced files...") 86 | reqBody := bytes.NewBuffer([]byte{}) 87 | _ = json.StringifyToWriter(reqBody, map[string]any{ 88 | "files": updateList, 89 | }) 90 | 91 | req, err := http.NewRequest("POST", config.ServerAddress+"/file/upload", reqBody) 92 | req.Header.Add("Content-Type", "application/json") 93 | token, err := configfile.GetValue("token") 94 | if err != nil { 95 | return err 96 | } 97 | req.Header.Add("Authorization", token) 98 | if err != nil { 99 | return err 100 | } 101 | resp, err := http.DefaultClient.Do(req) 102 | if err != nil { 103 | return err 104 | } 105 | 106 | if resp.StatusCode != 200 { 107 | respBody, _ := json.ParseFromReader[json.Json](resp.Body) 108 | resp.Body.Close() 109 | return errors.New(respBody["error"].(string)) 110 | } 111 | fmt.Fprintln(u.output, "done 👍") 112 | 113 | return nil 114 | } 115 | 116 | func (u *UploadFilesAction) listFiles() ([]string, error) { 117 | req, err := http.NewRequest("GET", config.ServerAddress+"/file", nil) 118 | token, err := configfile.GetValue("token") 119 | if err != nil { 120 | return nil, err 121 | } 122 | req.Header.Add("Authorization", token) 123 | if err != nil { 124 | return nil, err 125 | } 126 | resp, err := http.DefaultClient.Do(req) 127 | if err != nil { 128 | return nil, err 129 | } 130 | 131 | if resp.StatusCode != 200 { 132 | respBody, _ := json.ParseFromReader[json.Json](resp.Body) 133 | resp.Body.Close() 134 | return nil, errors.New(respBody["error"].(string)) 135 | } 136 | 137 | files, err := json.ParseFromReader[[]string](resp.Body) 138 | if err != nil { 139 | return nil, err 140 | } 141 | resp.Body.Close() 142 | 143 | return files, nil 144 | 145 | } 146 | -------------------------------------------------------------------------------- /config/config.go: -------------------------------------------------------------------------------- 1 | package config 2 | 3 | import "os/user" 4 | 5 | func init() { 6 | u, _ := user.Current() 7 | ConfigFilePath = u.HomeDir + "/.dotsyncrc" 8 | } 9 | 10 | var ( 11 | ServerAddress = "https://api.dotsync.org" 12 | ConfigFilePath = "~/.dotsyncrc" 13 | ) 14 | -------------------------------------------------------------------------------- /go.mod: -------------------------------------------------------------------------------- 1 | module github.com/mbaraa/dotsync 2 | 3 | go 1.20 4 | -------------------------------------------------------------------------------- /main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | "os" 6 | 7 | "github.com/mbaraa/dotsync/actions" 8 | ) 9 | 10 | const ( 11 | usageStr = `Usage of Dotsync: 12 | -login string:email 13 | login into your account, or create an account using a valid email. 14 | -delete-user 15 | delete your current logged in user, along with the remote files. 16 | -list 17 | lists the files that are currently synced. 18 | -add string:path 19 | adds a file or directory to the sync list. 20 | -remove string:file_path 21 | removes a file from the sync list. 22 | -download 23 | syncs local files with the server's version. 24 | -upload 25 | syncs server's files with the local version.` 26 | ) 27 | 28 | func main() { 29 | if len(os.Args) < 2 { 30 | fmt.Println(usageStr) 31 | os.Exit(1) 32 | } 33 | 34 | action := actions.GetAction(actions.ActionType(os.Args[1])) 35 | if action == nil { 36 | fmt.Println("Ivalid argument") 37 | fmt.Println() 38 | fmt.Println(usageStr) 39 | os.Exit(1) 40 | } 41 | 42 | if action.NeedsRoot() { 43 | fmt.Println("This action requires superuser to run") 44 | os.Exit(1) 45 | } 46 | 47 | var arg string 48 | if len(os.Args) > 2 { 49 | arg = os.Args[2] 50 | } 51 | 52 | if action.HasArgs() && len(arg) == 0 { 53 | fmt.Println("this action needs an argument") 54 | fmt.Println(usageStr) 55 | os.Exit(1) 56 | } 57 | 58 | err := action.Exec(os.Stdout, arg) 59 | if err != nil { 60 | fmt.Println(err.Error()) 61 | os.Exit(1) 62 | } 63 | } 64 | -------------------------------------------------------------------------------- /server/.dockerignore: -------------------------------------------------------------------------------- 1 | /_*/ 2 | /.elixir_ls/ 3 | /cover/ 4 | /deps/ 5 | /doc/ 6 | /.fetch 7 | erl_crash.dump 8 | *.ez 9 | /tmp/ 10 | dotsync-*.tar 11 | -------------------------------------------------------------------------------- /server/.env.example: -------------------------------------------------------------------------------- 1 | PORT= 2 | SECRET_KEY_BASE= 3 | JWT_SECRET= 4 | 5 | DB_NAME= 6 | DB_SERVER= 7 | DB_USERNAME= 8 | DB_PASSWORD= 9 | DB_URL="mysql://${DB_USERNAME}:${DB_PASSWORD}@tcp(${DB_SERVER})/${DB_NAME}?parseTime=True&loc=Local" 10 | 11 | SMTP_SERVER= 12 | SMTP_PORT= 13 | SMTP_USERNAME= 14 | SMTP_PASSWORD= 15 | -------------------------------------------------------------------------------- /server/.formatter.exs: -------------------------------------------------------------------------------- 1 | [ 2 | import_deps: [:ecto, :ecto_sql, :phoenix], 3 | subdirectories: ["priv/*/migrations"], 4 | inputs: ["*.{ex,exs}", "{config,lib,test}/**/*.{ex,exs}", "priv/*/seeds.exs"] 5 | ] 6 | -------------------------------------------------------------------------------- /server/.gitignore: -------------------------------------------------------------------------------- 1 | /_*/ 2 | /.elixir_ls/ 3 | /cover/ 4 | /deps/ 5 | /doc/ 6 | /.fetch 7 | erl_crash.dump 8 | *.ez 9 | /tmp/ 10 | dotsync-*.tar 11 | .env 12 | .env.* 13 | !.env.example 14 | -------------------------------------------------------------------------------- /server/CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Changelog 2 | 3 | All notable changes to this project will be documented in this file. 4 | 5 | The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), 6 | and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). 7 | 8 | ## \[[v0.1](https://github.com/mbaraa/dotsync_server/releases/tag/v0.1)\] 2023-08-26 9 | 10 | ### Added 11 | 12 | - remove directory implicitly 13 | 14 | ## \[[v0.0.1](https://github.com/mbaraa/dotsync_server/releases/tag/v0.0.1)\] 2023-08-24 15 | 16 | ### Added 17 | 18 | - an initial version of the server 19 | - add files/directories to the sync list 20 | - remove files from the sync list 21 | - update synced files 22 | - download synced files 23 | - login/create user 24 | - delete user 25 | -------------------------------------------------------------------------------- /server/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM elixir:1.14-alpine 2 | 3 | ARG PORT 4 | ARG SECRET_KEY_BASE 5 | ARG DB_NAME 6 | ARG DB_SERVER 7 | ARG DB_USERNAME 8 | ARG DB_PASSWORD 9 | ARG DB_URL 10 | ARG SMTP_SERVER 11 | ARG SMTP_PORT 12 | ARG SMTP_USERNAME 13 | ARG SMTP_PASSWORD 14 | 15 | ENV PORT=$PORT 16 | ENV SECRET_KEY_BASE=$SECRET_KEY_BASE 17 | ENV DB_NAME=$DB_NAME 18 | ENV DB_SERVER=$DB_SERVER 19 | ENV DB_USERNAME=$DB_USERNAME 20 | ENV DB_PASSWORD=$DB_PASSWORD 21 | ENV DB_URL=$DB_URL 22 | ENV SMTP_SERVER=$SMTP_SERVER 23 | ENV SMTP_PORT=$SMTP_PORT 24 | ENV SMTP_USERNAME=$SMTP_USERNAME 25 | ENV SMTP_PASSWORD=$SMTP_PASSWORD 26 | ENV MIX_ENV=prod 27 | 28 | RUN mkdir /app 29 | WORKDIR /app 30 | 31 | RUN apk add git ca-certificates wget gnupg gcc 32 | 33 | COPY priv priv 34 | 35 | COPY mix.exs mix.lock ./ 36 | COPY config config 37 | 38 | RUN mix local.hex --force && \ 39 | mix local.rebar --force && \ 40 | mix archive.install hex phx_new && \ 41 | mix deps.get --only prod 42 | 43 | COPY lib lib 44 | RUN mix compile 45 | 46 | COPY ./docker-entrypoint.sh /entrypoint.sh 47 | RUN chmod a+x /entrypoint.sh 48 | 49 | ENTRYPOINT [ "/entrypoint.sh" ] 50 | CMD ["run"] 51 | -------------------------------------------------------------------------------- /server/LICENSE: -------------------------------------------------------------------------------- 1 | GNU GENERAL PUBLIC LICENSE 2 | Version 3, 29 June 2007 3 | 4 | Copyright (C) 2007 Free Software Foundation, Inc. 5 | Everyone is permitted to copy and distribute verbatim copies 6 | of this license document, but changing it is not allowed. 7 | 8 | Preamble 9 | 10 | The GNU General Public License is a free, copyleft license for 11 | software and other kinds of works. 12 | 13 | The licenses for most software and other practical works are designed 14 | to take away your freedom to share and change the works. By contrast, 15 | the GNU General Public License is intended to guarantee your freedom to 16 | share and change all versions of a program--to make sure it remains free 17 | software for all its users. We, the Free Software Foundation, use the 18 | GNU General Public License for most of our software; it applies also to 19 | any other work released this way by its authors. You can apply it to 20 | your programs, too. 21 | 22 | When we speak of free software, we are referring to freedom, not 23 | price. Our General Public Licenses are designed to make sure that you 24 | have the freedom to distribute copies of free software (and charge for 25 | them if you wish), that you receive source code or can get it if you 26 | want it, that you can change the software or use pieces of it in new 27 | free programs, and that you know you can do these things. 28 | 29 | To protect your rights, we need to prevent others from denying you 30 | these rights or asking you to surrender the rights. Therefore, you have 31 | certain responsibilities if you distribute copies of the software, or if 32 | you modify it: responsibilities to respect the freedom of others. 33 | 34 | For example, if you distribute copies of such a program, whether 35 | gratis or for a fee, you must pass on to the recipients the same 36 | freedoms that you received. You must make sure that they, too, receive 37 | or can get the source code. And you must show them these terms so they 38 | know their rights. 39 | 40 | Developers that use the GNU GPL protect your rights with two steps: 41 | (1) assert copyright on the software, and (2) offer you this License 42 | giving you legal permission to copy, distribute and/or modify it. 43 | 44 | For the developers' and authors' protection, the GPL clearly explains 45 | that there is no warranty for this free software. For both users' and 46 | authors' sake, the GPL requires that modified versions be marked as 47 | changed, so that their problems will not be attributed erroneously to 48 | authors of previous versions. 49 | 50 | Some devices are designed to deny users access to install or run 51 | modified versions of the software inside them, although the manufacturer 52 | can do so. This is fundamentally incompatible with the aim of 53 | protecting users' freedom to change the software. The systematic 54 | pattern of such abuse occurs in the area of products for individuals to 55 | use, which is precisely where it is most unacceptable. Therefore, we 56 | have designed this version of the GPL to prohibit the practice for those 57 | products. If such problems arise substantially in other domains, we 58 | stand ready to extend this provision to those domains in future versions 59 | of the GPL, as needed to protect the freedom of users. 60 | 61 | Finally, every program is threatened constantly by software patents. 62 | States should not allow patents to restrict development and use of 63 | software on general-purpose computers, but in those that do, we wish to 64 | avoid the special danger that patents applied to a free program could 65 | make it effectively proprietary. To prevent this, the GPL assures that 66 | patents cannot be used to render the program non-free. 67 | 68 | The precise terms and conditions for copying, distribution and 69 | modification follow. 70 | 71 | TERMS AND CONDITIONS 72 | 73 | 0. Definitions. 74 | 75 | "This License" refers to version 3 of the GNU General Public License. 76 | 77 | "Copyright" also means copyright-like laws that apply to other kinds of 78 | works, such as semiconductor masks. 79 | 80 | "The Program" refers to any copyrightable work licensed under this 81 | License. Each licensee is addressed as "you". "Licensees" and 82 | "recipients" may be individuals or organizations. 83 | 84 | To "modify" a work means to copy from or adapt all or part of the work 85 | in a fashion requiring copyright permission, other than the making of an 86 | exact copy. The resulting work is called a "modified version" of the 87 | earlier work or a work "based on" the earlier work. 88 | 89 | A "covered work" means either the unmodified Program or a work based 90 | on the Program. 91 | 92 | To "propagate" a work means to do anything with it that, without 93 | permission, would make you directly or secondarily liable for 94 | infringement under applicable copyright law, except executing it on a 95 | computer or modifying a private copy. Propagation includes copying, 96 | distribution (with or without modification), making available to the 97 | public, and in some countries other activities as well. 98 | 99 | To "convey" a work means any kind of propagation that enables other 100 | parties to make or receive copies. Mere interaction with a user through 101 | a computer network, with no transfer of a copy, is not conveying. 102 | 103 | An interactive user interface displays "Appropriate Legal Notices" 104 | to the extent that it includes a convenient and prominently visible 105 | feature that (1) displays an appropriate copyright notice, and (2) 106 | tells the user that there is no warranty for the work (except to the 107 | extent that warranties are provided), that licensees may convey the 108 | work under this License, and how to view a copy of this License. If 109 | the interface presents a list of user commands or options, such as a 110 | menu, a prominent item in the list meets this criterion. 111 | 112 | 1. Source Code. 113 | 114 | The "source code" for a work means the preferred form of the work 115 | for making modifications to it. "Object code" means any non-source 116 | form of a work. 117 | 118 | A "Standard Interface" means an interface that either is an official 119 | standard defined by a recognized standards body, or, in the case of 120 | interfaces specified for a particular programming language, one that 121 | is widely used among developers working in that language. 122 | 123 | The "System Libraries" of an executable work include anything, other 124 | than the work as a whole, that (a) is included in the normal form of 125 | packaging a Major Component, but which is not part of that Major 126 | Component, and (b) serves only to enable use of the work with that 127 | Major Component, or to implement a Standard Interface for which an 128 | implementation is available to the public in source code form. A 129 | "Major Component", in this context, means a major essential component 130 | (kernel, window system, and so on) of the specific operating system 131 | (if any) on which the executable work runs, or a compiler used to 132 | produce the work, or an object code interpreter used to run it. 133 | 134 | The "Corresponding Source" for a work in object code form means all 135 | the source code needed to generate, install, and (for an executable 136 | work) run the object code and to modify the work, including scripts to 137 | control those activities. However, it does not include the work's 138 | System Libraries, or general-purpose tools or generally available free 139 | programs which are used unmodified in performing those activities but 140 | which are not part of the work. For example, Corresponding Source 141 | includes interface definition files associated with source files for 142 | the work, and the source code for shared libraries and dynamically 143 | linked subprograms that the work is specifically designed to require, 144 | such as by intimate data communication or control flow between those 145 | subprograms and other parts of the work. 146 | 147 | The Corresponding Source need not include anything that users 148 | can regenerate automatically from other parts of the Corresponding 149 | Source. 150 | 151 | The Corresponding Source for a work in source code form is that 152 | same work. 153 | 154 | 2. Basic Permissions. 155 | 156 | All rights granted under this License are granted for the term of 157 | copyright on the Program, and are irrevocable provided the stated 158 | conditions are met. This License explicitly affirms your unlimited 159 | permission to run the unmodified Program. The output from running a 160 | covered work is covered by this License only if the output, given its 161 | content, constitutes a covered work. This License acknowledges your 162 | rights of fair use or other equivalent, as provided by copyright law. 163 | 164 | You may make, run and propagate covered works that you do not 165 | convey, without conditions so long as your license otherwise remains 166 | in force. You may convey covered works to others for the sole purpose 167 | of having them make modifications exclusively for you, or provide you 168 | with facilities for running those works, provided that you comply with 169 | the terms of this License in conveying all material for which you do 170 | not control copyright. Those thus making or running the covered works 171 | for you must do so exclusively on your behalf, under your direction 172 | and control, on terms that prohibit them from making any copies of 173 | your copyrighted material outside their relationship with you. 174 | 175 | Conveying under any other circumstances is permitted solely under 176 | the conditions stated below. Sublicensing is not allowed; section 10 177 | makes it unnecessary. 178 | 179 | 3. Protecting Users' Legal Rights From Anti-Circumvention Law. 180 | 181 | No covered work shall be deemed part of an effective technological 182 | measure under any applicable law fulfilling obligations under article 183 | 11 of the WIPO copyright treaty adopted on 20 December 1996, or 184 | similar laws prohibiting or restricting circumvention of such 185 | measures. 186 | 187 | When you convey a covered work, you waive any legal power to forbid 188 | circumvention of technological measures to the extent such circumvention 189 | is effected by exercising rights under this License with respect to 190 | the covered work, and you disclaim any intention to limit operation or 191 | modification of the work as a means of enforcing, against the work's 192 | users, your or third parties' legal rights to forbid circumvention of 193 | technological measures. 194 | 195 | 4. Conveying Verbatim Copies. 196 | 197 | You may convey verbatim copies of the Program's source code as you 198 | receive it, in any medium, provided that you conspicuously and 199 | appropriately publish on each copy an appropriate copyright notice; 200 | keep intact all notices stating that this License and any 201 | non-permissive terms added in accord with section 7 apply to the code; 202 | keep intact all notices of the absence of any warranty; and give all 203 | recipients a copy of this License along with the Program. 204 | 205 | You may charge any price or no price for each copy that you convey, 206 | and you may offer support or warranty protection for a fee. 207 | 208 | 5. Conveying Modified Source Versions. 209 | 210 | You may convey a work based on the Program, or the modifications to 211 | produce it from the Program, in the form of source code under the 212 | terms of section 4, provided that you also meet all of these conditions: 213 | 214 | a) The work must carry prominent notices stating that you modified 215 | it, and giving a relevant date. 216 | 217 | b) The work must carry prominent notices stating that it is 218 | released under this License and any conditions added under section 219 | 7. This requirement modifies the requirement in section 4 to 220 | "keep intact all notices". 221 | 222 | c) You must license the entire work, as a whole, under this 223 | License to anyone who comes into possession of a copy. This 224 | License will therefore apply, along with any applicable section 7 225 | additional terms, to the whole of the work, and all its parts, 226 | regardless of how they are packaged. This License gives no 227 | permission to license the work in any other way, but it does not 228 | invalidate such permission if you have separately received it. 229 | 230 | d) If the work has interactive user interfaces, each must display 231 | Appropriate Legal Notices; however, if the Program has interactive 232 | interfaces that do not display Appropriate Legal Notices, your 233 | work need not make them do so. 234 | 235 | A compilation of a covered work with other separate and independent 236 | works, which are not by their nature extensions of the covered work, 237 | and which are not combined with it such as to form a larger program, 238 | in or on a volume of a storage or distribution medium, is called an 239 | "aggregate" if the compilation and its resulting copyright are not 240 | used to limit the access or legal rights of the compilation's users 241 | beyond what the individual works permit. Inclusion of a covered work 242 | in an aggregate does not cause this License to apply to the other 243 | parts of the aggregate. 244 | 245 | 6. Conveying Non-Source Forms. 246 | 247 | You may convey a covered work in object code form under the terms 248 | of sections 4 and 5, provided that you also convey the 249 | machine-readable Corresponding Source under the terms of this License, 250 | in one of these ways: 251 | 252 | a) Convey the object code in, or embodied in, a physical product 253 | (including a physical distribution medium), accompanied by the 254 | Corresponding Source fixed on a durable physical medium 255 | customarily used for software interchange. 256 | 257 | b) Convey the object code in, or embodied in, a physical product 258 | (including a physical distribution medium), accompanied by a 259 | written offer, valid for at least three years and valid for as 260 | long as you offer spare parts or customer support for that product 261 | model, to give anyone who possesses the object code either (1) a 262 | copy of the Corresponding Source for all the software in the 263 | product that is covered by this License, on a durable physical 264 | medium customarily used for software interchange, for a price no 265 | more than your reasonable cost of physically performing this 266 | conveying of source, or (2) access to copy the 267 | Corresponding Source from a network server at no charge. 268 | 269 | c) Convey individual copies of the object code with a copy of the 270 | written offer to provide the Corresponding Source. This 271 | alternative is allowed only occasionally and noncommercially, and 272 | only if you received the object code with such an offer, in accord 273 | with subsection 6b. 274 | 275 | d) Convey the object code by offering access from a designated 276 | place (gratis or for a charge), and offer equivalent access to the 277 | Corresponding Source in the same way through the same place at no 278 | further charge. You need not require recipients to copy the 279 | Corresponding Source along with the object code. If the place to 280 | copy the object code is a network server, the Corresponding Source 281 | may be on a different server (operated by you or a third party) 282 | that supports equivalent copying facilities, provided you maintain 283 | clear directions next to the object code saying where to find the 284 | Corresponding Source. Regardless of what server hosts the 285 | Corresponding Source, you remain obligated to ensure that it is 286 | available for as long as needed to satisfy these requirements. 287 | 288 | e) Convey the object code using peer-to-peer transmission, provided 289 | you inform other peers where the object code and Corresponding 290 | Source of the work are being offered to the general public at no 291 | charge under subsection 6d. 292 | 293 | A separable portion of the object code, whose source code is excluded 294 | from the Corresponding Source as a System Library, need not be 295 | included in conveying the object code work. 296 | 297 | A "User Product" is either (1) a "consumer product", which means any 298 | tangible personal property which is normally used for personal, family, 299 | or household purposes, or (2) anything designed or sold for incorporation 300 | into a dwelling. In determining whether a product is a consumer product, 301 | doubtful cases shall be resolved in favor of coverage. For a particular 302 | product received by a particular user, "normally used" refers to a 303 | typical or common use of that class of product, regardless of the status 304 | of the particular user or of the way in which the particular user 305 | actually uses, or expects or is expected to use, the product. A product 306 | is a consumer product regardless of whether the product has substantial 307 | commercial, industrial or non-consumer uses, unless such uses represent 308 | the only significant mode of use of the product. 309 | 310 | "Installation Information" for a User Product means any methods, 311 | procedures, authorization keys, or other information required to install 312 | and execute modified versions of a covered work in that User Product from 313 | a modified version of its Corresponding Source. The information must 314 | suffice to ensure that the continued functioning of the modified object 315 | code is in no case prevented or interfered with solely because 316 | modification has been made. 317 | 318 | If you convey an object code work under this section in, or with, or 319 | specifically for use in, a User Product, and the conveying occurs as 320 | part of a transaction in which the right of possession and use of the 321 | User Product is transferred to the recipient in perpetuity or for a 322 | fixed term (regardless of how the transaction is characterized), the 323 | Corresponding Source conveyed under this section must be accompanied 324 | by the Installation Information. But this requirement does not apply 325 | if neither you nor any third party retains the ability to install 326 | modified object code on the User Product (for example, the work has 327 | been installed in ROM). 328 | 329 | The requirement to provide Installation Information does not include a 330 | requirement to continue to provide support service, warranty, or updates 331 | for a work that has been modified or installed by the recipient, or for 332 | the User Product in which it has been modified or installed. Access to a 333 | network may be denied when the modification itself materially and 334 | adversely affects the operation of the network or violates the rules and 335 | protocols for communication across the network. 336 | 337 | Corresponding Source conveyed, and Installation Information provided, 338 | in accord with this section must be in a format that is publicly 339 | documented (and with an implementation available to the public in 340 | source code form), and must require no special password or key for 341 | unpacking, reading or copying. 342 | 343 | 7. Additional Terms. 344 | 345 | "Additional permissions" are terms that supplement the terms of this 346 | License by making exceptions from one or more of its conditions. 347 | Additional permissions that are applicable to the entire Program shall 348 | be treated as though they were included in this License, to the extent 349 | that they are valid under applicable law. If additional permissions 350 | apply only to part of the Program, that part may be used separately 351 | under those permissions, but the entire Program remains governed by 352 | this License without regard to the additional permissions. 353 | 354 | When you convey a copy of a covered work, you may at your option 355 | remove any additional permissions from that copy, or from any part of 356 | it. (Additional permissions may be written to require their own 357 | removal in certain cases when you modify the work.) You may place 358 | additional permissions on material, added by you to a covered work, 359 | for which you have or can give appropriate copyright permission. 360 | 361 | Notwithstanding any other provision of this License, for material you 362 | add to a covered work, you may (if authorized by the copyright holders of 363 | that material) supplement the terms of this License with terms: 364 | 365 | a) Disclaiming warranty or limiting liability differently from the 366 | terms of sections 15 and 16 of this License; or 367 | 368 | b) Requiring preservation of specified reasonable legal notices or 369 | author attributions in that material or in the Appropriate Legal 370 | Notices displayed by works containing it; or 371 | 372 | c) Prohibiting misrepresentation of the origin of that material, or 373 | requiring that modified versions of such material be marked in 374 | reasonable ways as different from the original version; or 375 | 376 | d) Limiting the use for publicity purposes of names of licensors or 377 | authors of the material; or 378 | 379 | e) Declining to grant rights under trademark law for use of some 380 | trade names, trademarks, or service marks; or 381 | 382 | f) Requiring indemnification of licensors and authors of that 383 | material by anyone who conveys the material (or modified versions of 384 | it) with contractual assumptions of liability to the recipient, for 385 | any liability that these contractual assumptions directly impose on 386 | those licensors and authors. 387 | 388 | All other non-permissive additional terms are considered "further 389 | restrictions" within the meaning of section 10. If the Program as you 390 | received it, or any part of it, contains a notice stating that it is 391 | governed by this License along with a term that is a further 392 | restriction, you may remove that term. If a license document contains 393 | a further restriction but permits relicensing or conveying under this 394 | License, you may add to a covered work material governed by the terms 395 | of that license document, provided that the further restriction does 396 | not survive such relicensing or conveying. 397 | 398 | If you add terms to a covered work in accord with this section, you 399 | must place, in the relevant source files, a statement of the 400 | additional terms that apply to those files, or a notice indicating 401 | where to find the applicable terms. 402 | 403 | Additional terms, permissive or non-permissive, may be stated in the 404 | form of a separately written license, or stated as exceptions; 405 | the above requirements apply either way. 406 | 407 | 8. Termination. 408 | 409 | You may not propagate or modify a covered work except as expressly 410 | provided under this License. Any attempt otherwise to propagate or 411 | modify it is void, and will automatically terminate your rights under 412 | this License (including any patent licenses granted under the third 413 | paragraph of section 11). 414 | 415 | However, if you cease all violation of this License, then your 416 | license from a particular copyright holder is reinstated (a) 417 | provisionally, unless and until the copyright holder explicitly and 418 | finally terminates your license, and (b) permanently, if the copyright 419 | holder fails to notify you of the violation by some reasonable means 420 | prior to 60 days after the cessation. 421 | 422 | Moreover, your license from a particular copyright holder is 423 | reinstated permanently if the copyright holder notifies you of the 424 | violation by some reasonable means, this is the first time you have 425 | received notice of violation of this License (for any work) from that 426 | copyright holder, and you cure the violation prior to 30 days after 427 | your receipt of the notice. 428 | 429 | Termination of your rights under this section does not terminate the 430 | licenses of parties who have received copies or rights from you under 431 | this License. If your rights have been terminated and not permanently 432 | reinstated, you do not qualify to receive new licenses for the same 433 | material under section 10. 434 | 435 | 9. Acceptance Not Required for Having Copies. 436 | 437 | You are not required to accept this License in order to receive or 438 | run a copy of the Program. Ancillary propagation of a covered work 439 | occurring solely as a consequence of using peer-to-peer transmission 440 | to receive a copy likewise does not require acceptance. However, 441 | nothing other than this License grants you permission to propagate or 442 | modify any covered work. These actions infringe copyright if you do 443 | not accept this License. Therefore, by modifying or propagating a 444 | covered work, you indicate your acceptance of this License to do so. 445 | 446 | 10. Automatic Licensing of Downstream Recipients. 447 | 448 | Each time you convey a covered work, the recipient automatically 449 | receives a license from the original licensors, to run, modify and 450 | propagate that work, subject to this License. You are not responsible 451 | for enforcing compliance by third parties with this License. 452 | 453 | An "entity transaction" is a transaction transferring control of an 454 | organization, or substantially all assets of one, or subdividing an 455 | organization, or merging organizations. If propagation of a covered 456 | work results from an entity transaction, each party to that 457 | transaction who receives a copy of the work also receives whatever 458 | licenses to the work the party's predecessor in interest had or could 459 | give under the previous paragraph, plus a right to possession of the 460 | Corresponding Source of the work from the predecessor in interest, if 461 | the predecessor has it or can get it with reasonable efforts. 462 | 463 | You may not impose any further restrictions on the exercise of the 464 | rights granted or affirmed under this License. For example, you may 465 | not impose a license fee, royalty, or other charge for exercise of 466 | rights granted under this License, and you may not initiate litigation 467 | (including a cross-claim or counterclaim in a lawsuit) alleging that 468 | any patent claim is infringed by making, using, selling, offering for 469 | sale, or importing the Program or any portion of it. 470 | 471 | 11. Patents. 472 | 473 | A "contributor" is a copyright holder who authorizes use under this 474 | License of the Program or a work on which the Program is based. The 475 | work thus licensed is called the contributor's "contributor version". 476 | 477 | A contributor's "essential patent claims" are all patent claims 478 | owned or controlled by the contributor, whether already acquired or 479 | hereafter acquired, that would be infringed by some manner, permitted 480 | by this License, of making, using, or selling its contributor version, 481 | but do not include claims that would be infringed only as a 482 | consequence of further modification of the contributor version. For 483 | purposes of this definition, "control" includes the right to grant 484 | patent sublicenses in a manner consistent with the requirements of 485 | this License. 486 | 487 | Each contributor grants you a non-exclusive, worldwide, royalty-free 488 | patent license under the contributor's essential patent claims, to 489 | make, use, sell, offer for sale, import and otherwise run, modify and 490 | propagate the contents of its contributor version. 491 | 492 | In the following three paragraphs, a "patent license" is any express 493 | agreement or commitment, however denominated, not to enforce a patent 494 | (such as an express permission to practice a patent or covenant not to 495 | sue for patent infringement). To "grant" such a patent license to a 496 | party means to make such an agreement or commitment not to enforce a 497 | patent against the party. 498 | 499 | If you convey a covered work, knowingly relying on a patent license, 500 | and the Corresponding Source of the work is not available for anyone 501 | to copy, free of charge and under the terms of this License, through a 502 | publicly available network server or other readily accessible means, 503 | then you must either (1) cause the Corresponding Source to be so 504 | available, or (2) arrange to deprive yourself of the benefit of the 505 | patent license for this particular work, or (3) arrange, in a manner 506 | consistent with the requirements of this License, to extend the patent 507 | license to downstream recipients. "Knowingly relying" means you have 508 | actual knowledge that, but for the patent license, your conveying the 509 | covered work in a country, or your recipient's use of the covered work 510 | in a country, would infringe one or more identifiable patents in that 511 | country that you have reason to believe are valid. 512 | 513 | If, pursuant to or in connection with a single transaction or 514 | arrangement, you convey, or propagate by procuring conveyance of, a 515 | covered work, and grant a patent license to some of the parties 516 | receiving the covered work authorizing them to use, propagate, modify 517 | or convey a specific copy of the covered work, then the patent license 518 | you grant is automatically extended to all recipients of the covered 519 | work and works based on it. 520 | 521 | A patent license is "discriminatory" if it does not include within 522 | the scope of its coverage, prohibits the exercise of, or is 523 | conditioned on the non-exercise of one or more of the rights that are 524 | specifically granted under this License. You may not convey a covered 525 | work if you are a party to an arrangement with a third party that is 526 | in the business of distributing software, under which you make payment 527 | to the third party based on the extent of your activity of conveying 528 | the work, and under which the third party grants, to any of the 529 | parties who would receive the covered work from you, a discriminatory 530 | patent license (a) in connection with copies of the covered work 531 | conveyed by you (or copies made from those copies), or (b) primarily 532 | for and in connection with specific products or compilations that 533 | contain the covered work, unless you entered into that arrangement, 534 | or that patent license was granted, prior to 28 March 2007. 535 | 536 | Nothing in this License shall be construed as excluding or limiting 537 | any implied license or other defenses to infringement that may 538 | otherwise be available to you under applicable patent law. 539 | 540 | 12. No Surrender of Others' Freedom. 541 | 542 | If conditions are imposed on you (whether by court order, agreement or 543 | otherwise) that contradict the conditions of this License, they do not 544 | excuse you from the conditions of this License. If you cannot convey a 545 | covered work so as to satisfy simultaneously your obligations under this 546 | License and any other pertinent obligations, then as a consequence you may 547 | not convey it at all. For example, if you agree to terms that obligate you 548 | to collect a royalty for further conveying from those to whom you convey 549 | the Program, the only way you could satisfy both those terms and this 550 | License would be to refrain entirely from conveying the Program. 551 | 552 | 13. Use with the GNU Affero General Public License. 553 | 554 | Notwithstanding any other provision of this License, you have 555 | permission to link or combine any covered work with a work licensed 556 | under version 3 of the GNU Affero General Public License into a single 557 | combined work, and to convey the resulting work. The terms of this 558 | License will continue to apply to the part which is the covered work, 559 | but the special requirements of the GNU Affero General Public License, 560 | section 13, concerning interaction through a network will apply to the 561 | combination as such. 562 | 563 | 14. Revised Versions of this License. 564 | 565 | The Free Software Foundation may publish revised and/or new versions of 566 | the GNU General Public License from time to time. Such new versions will 567 | be similar in spirit to the present version, but may differ in detail to 568 | address new problems or concerns. 569 | 570 | Each version is given a distinguishing version number. If the 571 | Program specifies that a certain numbered version of the GNU General 572 | Public License "or any later version" applies to it, you have the 573 | option of following the terms and conditions either of that numbered 574 | version or of any later version published by the Free Software 575 | Foundation. If the Program does not specify a version number of the 576 | GNU General Public License, you may choose any version ever published 577 | by the Free Software Foundation. 578 | 579 | If the Program specifies that a proxy can decide which future 580 | versions of the GNU General Public License can be used, that proxy's 581 | public statement of acceptance of a version permanently authorizes you 582 | to choose that version for the Program. 583 | 584 | Later license versions may give you additional or different 585 | permissions. However, no additional obligations are imposed on any 586 | author or copyright holder as a result of your choosing to follow a 587 | later version. 588 | 589 | 15. Disclaimer of Warranty. 590 | 591 | THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY 592 | APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT 593 | HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY 594 | OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, 595 | THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR 596 | PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM 597 | IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF 598 | ALL NECESSARY SERVICING, REPAIR OR CORRECTION. 599 | 600 | 16. Limitation of Liability. 601 | 602 | IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING 603 | WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS 604 | THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY 605 | GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE 606 | USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF 607 | DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD 608 | PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), 609 | EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF 610 | SUCH DAMAGES. 611 | 612 | 17. Interpretation of Sections 15 and 16. 613 | 614 | If the disclaimer of warranty and limitation of liability provided 615 | above cannot be given local legal effect according to their terms, 616 | reviewing courts shall apply local law that most closely approximates 617 | an absolute waiver of all civil liability in connection with the 618 | Program, unless a warranty or assumption of liability accompanies a 619 | copy of the Program in return for a fee. 620 | 621 | END OF TERMS AND CONDITIONS 622 | 623 | How to Apply These Terms to Your New Programs 624 | 625 | If you develop a new program, and you want it to be of the greatest 626 | possible use to the public, the best way to achieve this is to make it 627 | free software which everyone can redistribute and change under these terms. 628 | 629 | To do so, attach the following notices to the program. It is safest 630 | to attach them to the start of each source file to most effectively 631 | state the exclusion of warranty; and each file should have at least 632 | the "copyright" line and a pointer to where the full notice is found. 633 | 634 | 635 | Copyright (C) 636 | 637 | This program is free software: you can redistribute it and/or modify 638 | it under the terms of the GNU General Public License as published by 639 | the Free Software Foundation, either version 3 of the License, or 640 | (at your option) any later version. 641 | 642 | This program is distributed in the hope that it will be useful, 643 | but WITHOUT ANY WARRANTY; without even the implied warranty of 644 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 645 | GNU General Public License for more details. 646 | 647 | You should have received a copy of the GNU General Public License 648 | along with this program. If not, see . 649 | 650 | Also add information on how to contact you by electronic and paper mail. 651 | 652 | If the program does terminal interaction, make it output a short 653 | notice like this when it starts in an interactive mode: 654 | 655 | Copyright (C) 656 | This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'. 657 | This is free software, and you are welcome to redistribute it 658 | under certain conditions; type `show c' for details. 659 | 660 | The hypothetical commands `show w' and `show c' should show the appropriate 661 | parts of the General Public License. Of course, your program's commands 662 | might be different; for a GUI interface, you would use an "about box". 663 | 664 | You should also get your employer (if you work as a programmer) or school, 665 | if any, to sign a "copyright disclaimer" for the program, if necessary. 666 | For more information on this, and how to apply and follow the GNU GPL, see 667 | . 668 | 669 | The GNU General Public License does not permit incorporating your program 670 | into proprietary programs. If your program is a subroutine library, you 671 | may consider it more useful to permit linking proprietary applications with 672 | the library. If this is what you want to do, use the GNU Lesser General 673 | Public License instead of this License. But first, please read 674 | . 675 | 676 | -------------------------------------------------------------------------------- /server/README.md: -------------------------------------------------------------------------------- 1 | # Dotsync's server 2 | 3 | [![server-rex-deploy](https://github.com/mbaraa/dotsync/actions/workflows/server-rex-deploy.yml/badge.svg)](https://github.com/mbaraa/dotsync/actions/workflows/server-rex-deploy.yml) 4 | 5 | This is the server of [Dotsync](https://github.com/mbaraa/dotsync), where it stores all the synced dotfiles securely. 6 | 7 | ### Self-hosting: 8 | 9 | #### You'll need: 10 | 11 | 1. Docker and Docker Compose 12 | 1. An internet connection 13 | 1. A valid SMTP server 14 | 1. ~314 seconds, Elixir likes to take a lot of time to build... 15 | 16 | #### You'll do: 17 | 18 | 1. Clone the repo 19 | 1. Modify database's password in [docker-compose.yml](./docker-compose.yml) (just do a find and replace) 20 | 1. Copy `.env.example` into `.env.docker` 21 | 1. Set the environmental values, port, secrets, credentials... 22 | 1. Run `docker compose up -d` 23 | 24 | - (Optional) use [Rex](https://github.com/mbaraa/rex) for a minimalistic deployment automation. 25 | -------------------------------------------------------------------------------- /server/config/config.exs: -------------------------------------------------------------------------------- 1 | # This file is responsible for configuring your application 2 | # and its dependencies with the aid of the Config module. 3 | # 4 | # This configuration file is loaded before any dependency and 5 | # is restricted to this project. 6 | 7 | # General application configuration 8 | import Config 9 | 10 | config :dotsync, 11 | ecto_repos: [Dotsync.Repo] 12 | 13 | # Configures the endpoint 14 | config :dotsync, DotsyncWeb.Endpoint, 15 | url: [host: "0.0.0.0"], 16 | render_errors: [ 17 | formats: [json: DotsyncWeb.ErrorJSON], 18 | layout: false 19 | ], 20 | pubsub_server: Dotsync.PubSub, 21 | live_view: [signing_salt: "1O+mfL/D"] 22 | 23 | # Configures the mailer 24 | config :swoosh, :api_client, false 25 | 26 | config :dotsync, Dotsync.Mailer, 27 | adapter: Swoosh.Adapters.SMTP, 28 | relay: System.get_env("SMTP_SERVER"), 29 | port: System.get_env("SMTP_PORT") || "0" |> String.to_integer(), 30 | username: System.get_env("SMTP_USERNAME"), 31 | password: System.get_env("SMTP_PASSWORD"), 32 | auth: :always, 33 | ssl: false, 34 | tls: :always, 35 | retries: 0, 36 | no_mx_lookups: false, 37 | tls_options: [ 38 | versions: [:"tlsv1.1", :"tlsv1.2", :"tlsv1.3"], 39 | verify: :verify_peer, 40 | cacerts: :public_key.cacerts_get(), 41 | server_name_indication: ~c"#{System.get_env("SMTP_SERVER")}", 42 | depth: 99 43 | ] 44 | 45 | # Configures Elixir's Logger 46 | config :logger, :console, 47 | format: "$time $metadata[$level] $message\n", 48 | metadata: [:request_id] 49 | 50 | # Use Jason for JSON parsing in Phoenix 51 | config :phoenix, :json_library, Jason 52 | 53 | # Joken, jwt signer 54 | config :joken, default_signer: System.get_env("JWT_SECRET") 55 | # config :dotsync, Dotsync.Jwt, default_signer: System.get_env("JWT_SECRET") 56 | 57 | # Import environment specific config. This must remain at the bottom 58 | # of this file so it overrides the configuration defined above. 59 | import_config "#{config_env()}.exs" 60 | -------------------------------------------------------------------------------- /server/config/dev.exs: -------------------------------------------------------------------------------- 1 | import Config 2 | 3 | # Configure your database 4 | config :dotsync, Dotsync.Repo, 5 | username: System.get_env("DB_USERNAME"), 6 | password: System.get_env("DB_PASSWORD"), 7 | hostname: System.get_env("DB_SERVER"), 8 | database: System.get_env("DB_NAME"), 9 | stacktrace: true, 10 | show_sensitive_data_on_connection_error: true, 11 | pool_size: 10 12 | 13 | # For development, we disable any cache and enable 14 | # debugging and code reloading. 15 | # 16 | # The watchers configuration can be used to run external 17 | # watchers to your application. For example, we can use it 18 | # to bundle .js and .css sources. 19 | config :dotsync, DotsyncWeb.Endpoint, 20 | # Binding to loopback ipv4 address prevents access from other machines. 21 | # Change to `ip: {0, 0, 0, 0}` to allow access from other machines. 22 | http: [ip: {0, 0, 0, 0}, port: System.get_env("PORT") || "8080" |> String.to_integer()], 23 | check_origin: false, 24 | code_reloader: true, 25 | debug_errors: true, 26 | secret_key_base: "5Mf7ogNsjeSpAowPeuka4K94XMZVCGfM+/w8Tm0iFq2exF3wkbPuYt0+vKU5KHmb", 27 | watchers: [] 28 | 29 | # ## SSL Support 30 | # 31 | # In order to use HTTPS in development, a self-signed 32 | # certificate can be generated by running the following 33 | # Mix task: 34 | # 35 | # mix phx.gen.cert 36 | # 37 | # Run `mix help phx.gen.cert` for more information. 38 | # 39 | # The `http:` config above can be replaced with: 40 | # 41 | # https: [ 42 | # port: 4001, 43 | # cipher_suite: :strong, 44 | # keyfile: "priv/cert/selfsigned_key.pem", 45 | # certfile: "priv/cert/selfsigned.pem" 46 | # ], 47 | # 48 | # If desired, both `http:` and `https:` keys can be 49 | # configured to run both http and https servers on 50 | # different ports. 51 | 52 | # Enable dev routes for dashboard and mailbox 53 | config :dotsync, dev_routes: true 54 | 55 | # Do not include metadata nor timestamps in development logs 56 | config :logger, :console, format: "[$level] $message\n" 57 | 58 | # Set a higher stacktrace during development. Avoid configuring such 59 | # in production as building large stacktraces may be expensive. 60 | config :phoenix, :stacktrace_depth, 20 61 | 62 | # Initialize plugs at runtime for faster development compilation 63 | config :phoenix, :plug_init_mode, :runtime 64 | 65 | # Disable swoosh api client as it is only required for production adapters. 66 | # config :swoosh, :api_client, false 67 | -------------------------------------------------------------------------------- /server/config/prod.exs: -------------------------------------------------------------------------------- 1 | import Config 2 | 3 | # Configures Swoosh API Client 4 | # config :swoosh, api_client: Swoosh.ApiClient.Finch, finch_name: Dotsync.Finch 5 | 6 | # Disable Swoosh Local Memory Storage 7 | # config :swoosh, local: false 8 | 9 | # Do not print debug messages in production 10 | config :logger, level: :info 11 | 12 | # Runtime production configuration, including reading 13 | # of environment variables, is done on config/runtime.exs. 14 | -------------------------------------------------------------------------------- /server/config/runtime.exs: -------------------------------------------------------------------------------- 1 | import Config 2 | 3 | # config/runtime.exs is executed for all environments, including 4 | # during releases. It is executed after compilation and before the 5 | # system starts, so it is typically used to load production configuration 6 | # and secrets from environment variables or elsewhere. Do not define 7 | # any compile-time configuration in here, as it won't be applied. 8 | # The block below contains prod specific runtime configuration. 9 | 10 | # ## Using releases 11 | # 12 | # If you use `mix release`, you need to explicitly enable the server 13 | # by passing the PHX_SERVER=true when you start it: 14 | # 15 | # PHX_SERVER=true bin/dotsync start 16 | # 17 | # Alternatively, you can use `mix phx.gen.release` to generate a `bin/server` 18 | # script that automatically sets the env var above. 19 | if System.get_env("PHX_SERVER") do 20 | config :dotsync, DotsyncWeb.Endpoint, server: true 21 | end 22 | 23 | if config_env() == :prod do 24 | database_url = 25 | System.get_env("DB_URL") || 26 | raise """ 27 | environment variable DB_URL is missing. 28 | For example: ecto://USER:PASS@HOST/DATABASE 29 | """ 30 | 31 | maybe_ipv6 = if System.get_env("ECTO_IPV6") in ~w(true 1), do: [:inet6], else: [] 32 | 33 | config :dotsync, Dotsync.Repo, 34 | # ssl: true, 35 | url: database_url, 36 | pool_size: String.to_integer(System.get_env("POOL_SIZE") || "10"), 37 | socket_options: maybe_ipv6 38 | 39 | # The secret key base is used to sign/encrypt cookies and other secrets. 40 | # A default value is used in config/dev.exs and config/test.exs but you 41 | # want to use a different value for prod and you most likely don't want 42 | # to check this value into version control, so we use an environment 43 | # variable instead. 44 | secret_key_base = 45 | System.get_env("SECRET_KEY_BASE") || 46 | raise """ 47 | environment variable SECRET_KEY_BASE is missing. 48 | You can generate one by calling: mix phx.gen.secret 49 | """ 50 | 51 | host = System.get_env("PHX_HOST") || "example.com" 52 | port = String.to_integer(System.get_env("PORT") || "8080") 53 | 54 | config :dotsync, DotsyncWeb.Endpoint, 55 | url: [host: host, port: 443, scheme: "https"], 56 | http: [ 57 | # Enable IPv6 and bind on all interfaces. 58 | # Set it to {0, 0, 0, 0, 0, 0, 0, 1} for local network only access. 59 | # See the documentation on https://hexdocs.pm/plug_cowboy/Plug.Cowboy.html 60 | # for details about using IPv6 vs IPv4 and loopback vs public addresses. 61 | ip: {0, 0, 0, 0, 0, 0, 0, 0}, 62 | port: port 63 | ], 64 | secret_key_base: secret_key_base 65 | 66 | # ## SSL Support 67 | # 68 | # To get SSL working, you will need to add the `https` key 69 | # to your endpoint configuration: 70 | # 71 | # config :dotsync, DotsyncWeb.Endpoint, 72 | # https: [ 73 | # ..., 74 | # port: 443, 75 | # cipher_suite: :strong, 76 | # keyfile: System.get_env("SOME_APP_SSL_KEY_PATH"), 77 | # certfile: System.get_env("SOME_APP_SSL_CERT_PATH") 78 | # ] 79 | # 80 | # The `cipher_suite` is set to `:strong` to support only the 81 | # latest and more secure SSL ciphers. This means old browsers 82 | # and clients may not be supported. You can set it to 83 | # `:compatible` for wider support. 84 | # 85 | # `:keyfile` and `:certfile` expect an absolute path to the key 86 | # and cert in disk or a relative path inside priv, for example 87 | # "priv/ssl/server.key". For all supported SSL configuration 88 | # options, see https://hexdocs.pm/plug/Plug.SSL.html#configure/1 89 | # 90 | # We also recommend setting `force_ssl` in your endpoint, ensuring 91 | # no data is ever sent via http, always redirecting to https: 92 | # 93 | # config :dotsync, DotsyncWeb.Endpoint, 94 | # force_ssl: [hsts: true] 95 | # 96 | # Check `Plug.SSL` for all available options in `force_ssl`. 97 | 98 | # ## Configuring the mailer 99 | # 100 | # In production you need to configure the mailer to use a different adapter. 101 | # Also, you may need to configure the Swoosh API client of your choice if you 102 | # are not using SMTP. Here is an example of the configuration: 103 | # 104 | # config :dotsync, Dotsync.Mailer, 105 | # adapter: Swoosh.Adapters.Mailgun, 106 | # api_key: System.get_env("MAILGUN_API_KEY"), 107 | # domain: System.get_env("MAILGUN_DOMAIN") 108 | # 109 | # For this example you need include a HTTP client required by Swoosh API client. 110 | # Swoosh supports Hackney and Finch out of the box: 111 | # 112 | # config :swoosh, :api_client, Swoosh.ApiClient.Hackney 113 | # 114 | # See https://hexdocs.pm/swoosh/Swoosh.html#module-installation for details. 115 | end 116 | -------------------------------------------------------------------------------- /server/config/test.exs: -------------------------------------------------------------------------------- 1 | import Config 2 | 3 | # Configure your database 4 | # 5 | # The MIX_TEST_PARTITION environment variable can be used 6 | # to provide built-in test partitioning in CI environment. 7 | # Run `mix help test` for more information. 8 | config :dotsync, Dotsync.Repo, 9 | username: "root", 10 | password: "", 11 | hostname: "localhost", 12 | database: "dotsync_test#{System.get_env("MIX_TEST_PARTITION")}", 13 | pool: Ecto.Adapters.SQL.Sandbox, 14 | pool_size: 10 15 | 16 | # We don't run a server during test. If one is required, 17 | # you can enable the server option below. 18 | config :dotsync, DotsyncWeb.Endpoint, 19 | http: [ip: {127, 0, 0, 1}, port: 4002], 20 | secret_key_base: "FrBKfhyX4G89Kp7vmAUg8mm20JQg1mY8icW6y0Yho30goPhUCadeERvlle+sNV2u", 21 | server: false 22 | 23 | # In test we don't send emails. 24 | config :dotsync, Dotsync.Mailer, adapter: Swoosh.Adapters.Test 25 | 26 | # Disable swoosh api client as it is only required for production adapters. 27 | # config :swoosh, :api_client, false 28 | 29 | # Print only warnings and errors during test 30 | config :logger, level: :warning 31 | 32 | # Initialize plugs at runtime for faster test compilation 33 | config :phoenix, :plug_init_mode, :runtime 34 | -------------------------------------------------------------------------------- /server/docker-compose.yml: -------------------------------------------------------------------------------- 1 | version: "3.8" 2 | 3 | services: 4 | dotsync-db: 5 | image: "mariadb:10.6" 6 | container_name: "dotsync-db" 7 | restart: "always" 8 | environment: 9 | MARIADB_ROOT_PASSWORD: "hello" 10 | MARIADB_DATABASE: "dotsync" 11 | ports: 12 | - "3306" 13 | volumes: 14 | - db-config:/etc/mysql 15 | - db-data:/var/lib/mysql 16 | networks: 17 | - dotsync 18 | 19 | dotsync-server: 20 | build: 21 | dockerfile: Dockerfile 22 | context: "." 23 | args: 24 | - PORT 25 | - SECRET_KEY_BASE 26 | - DB_NAME 27 | - DB_SERVER 28 | - DB_USERNAME 29 | - DB_PASSWORD 30 | - DB_URL="mysql://root:hello@dotsync-db/dotsync?parseTime=True&loc=Local" 31 | - SMTP_SERVER 32 | - SMTP_PORT 33 | - SMTP_USERNAME 34 | - SMTP_PASSWORD 35 | 36 | container_name: "dotsync-server" 37 | depends_on: 38 | - dotsync-db 39 | # restart: "unless-stopped" 40 | restart: "always" 41 | ports: 42 | - "2388:8080" 43 | env_file: ./.env.docker 44 | environment: 45 | UPLOAD_DIRECTORY: "/uploads" 46 | volumes: 47 | - upload-dir:/uploads 48 | stdin_open: true 49 | networks: 50 | - dotsync 51 | 52 | volumes: 53 | db-config: 54 | driver: local 55 | driver_opts: 56 | type: none 57 | o: bind 58 | device: ./_database/etc/ 59 | db-data: 60 | driver: local 61 | driver_opts: 62 | type: none 63 | o: bind 64 | device: ./_database/var/ 65 | upload-dir: 66 | driver: local 67 | driver_opts: 68 | type: none 69 | o: bind 70 | device: ./_uploads/ 71 | 72 | networks: 73 | dotsync: {} 74 | -------------------------------------------------------------------------------- /server/docker-entrypoint.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | set -e 4 | 5 | if [[ "$1" = "run" ]] 6 | then 7 | mix ecto.create 8 | mix ecto.migrate 9 | mix phx.server 10 | else 11 | exec "$@" 12 | fi 13 | -------------------------------------------------------------------------------- /server/lib/dotsync.ex: -------------------------------------------------------------------------------- 1 | defmodule Dotsync do 2 | @moduledoc """ 3 | Dotsync keeps the contexts that define your domain 4 | and business logic. 5 | 6 | Contexts are also responsible for managing your data, regardless 7 | if it comes from the database, an external API or others. 8 | """ 9 | end 10 | -------------------------------------------------------------------------------- /server/lib/dotsync/application.ex: -------------------------------------------------------------------------------- 1 | defmodule Dotsync.Application do 2 | # See https://hexdocs.pm/elixir/Application.html 3 | # for more information on OTP Applications 4 | @moduledoc false 5 | 6 | use Application 7 | 8 | @impl true 9 | def start(_type, _args) do 10 | children = [ 11 | # Start the Telemetry supervisor 12 | DotsyncWeb.Telemetry, 13 | # Start the Ecto repository 14 | Dotsync.Repo, 15 | # Start the PubSub system 16 | {Phoenix.PubSub, name: Dotsync.PubSub}, 17 | # Start Finch 18 | {Finch, name: Swoosh.Finch}, 19 | # Start the Endpoint (http/https) 20 | DotsyncWeb.Endpoint 21 | # Start a worker by calling: Dotsync.Worker.start_link(arg) 22 | # {Dotsync.Worker, arg} 23 | ] 24 | 25 | # See https://hexdocs.pm/elixir/Supervisor.html 26 | # for other strategies and supported options 27 | opts = [strategy: :one_for_one, name: Dotsync.Supervisor] 28 | Supervisor.start_link(children, opts) 29 | end 30 | 31 | # Tell Phoenix to update the endpoint configuration 32 | # whenever the application is updated. 33 | @impl true 34 | def config_change(changed, _new, removed) do 35 | DotsyncWeb.Endpoint.config_change(changed, removed) 36 | :ok 37 | end 38 | end 39 | -------------------------------------------------------------------------------- /server/lib/dotsync/jwt.ex: -------------------------------------------------------------------------------- 1 | defmodule Dotsync.Jwt do 2 | use Joken.Config 3 | 4 | def token_config() do 5 | default_claims() 6 | |> add_claim("iss", fn -> "Dotsync" end) 7 | |> add_claim("aud", fn -> "Dotsync" end) 8 | |> add_claim("exp", fn -> :os.system_time(:seconds) + 30 * 60 end) 9 | end 10 | 11 | @spec check_callback(token :: String.t(), callback :: function()) :: 12 | {:ok, any()} | {:error, atom()} 13 | def(check_callback(token, callback)) do 14 | case check(token) do 15 | {:ok, claims} -> claims |> callback.() 16 | {:error, reason} -> {:error, reason} 17 | end 18 | end 19 | 20 | @spec check(token :: String.t()) :: {:ok, any()} | {:error, atom()} 21 | def check(token) do 22 | case token 23 | |> String.trim() 24 | |> verify() do 25 | {:ok, claims} -> 26 | cond do 27 | Map.get(claims, "exp") < :os.system_time(:seconds) -> {:error, :expired_token} 28 | true -> {:ok, claims} 29 | end 30 | 31 | {:error, _reason} -> 32 | {:error, :invalid_token} 33 | end 34 | end 35 | end 36 | -------------------------------------------------------------------------------- /server/lib/dotsync/mailer.ex: -------------------------------------------------------------------------------- 1 | defmodule Dotsync.Mailer do 2 | use Swoosh.Mailer, otp_app: :dotsync 3 | import Swoosh.Email 4 | 5 | @typedoc """ 6 | A valid email address, and login token. 7 | """ 8 | @type token :: charlist() 9 | @type email :: charlist() 10 | def send_login_token(token, email) do 11 | new() 12 | |> to(email) 13 | |> from({"Dotsync", "pub@mbaraa.com"}) 14 | |> subject("Login token") 15 | |> html_body(get_login_token_html(token)) 16 | |> text_body("Hello, here's your login token: #{token}, DON'T SHARE IT WITH ANYONE!") 17 | |> Dotsync.Mailer.deliver() 18 | end 19 | 20 | defp get_login_token_html(token) do 21 | """ 22 | 23 | 24 | 27 | 28 | 29 |
37 |
38 |
39 | Dotsync 49 |
50 |

Hi,

51 |

52 | Thanks for using Dotsync, keep in mind that your email and data, 53 | are not shared or viewed by anyone!
And below lies 54 | your login token, don't share it with anyone, in order to keep your 55 | account safe 😁 56 |

57 |

70 | #{token} 71 |

72 |

73 | Regards,
Baraa Al-Masri :: Dotsync's Admin 74 |

75 | 76 |
77 |
87 |

Dotsync

88 |

89 | pub@mbaraa.com 90 |
91 | mbaraa.com 92 |
93 | dotsync.org 94 |

95 |
96 |
97 |
98 | 99 | 100 | """ 101 | end 102 | end 103 | -------------------------------------------------------------------------------- /server/lib/dotsync/repo.ex: -------------------------------------------------------------------------------- 1 | defmodule Dotsync.Repo do 2 | use Ecto.Repo, 3 | otp_app: :dotsync, 4 | adapter: Ecto.Adapters.MyXQL 5 | end 6 | -------------------------------------------------------------------------------- /server/lib/dotsync/schemas/file.ex: -------------------------------------------------------------------------------- 1 | defmodule Dotsync.Schemas.File do 2 | use Ecto.Schema 3 | import Ecto.Changeset 4 | 5 | schema "files" do 6 | field :path, :string 7 | field :content, :string 8 | belongs_to :user, Dotsync.Schemas.User, foreign_key: :user_id 9 | 10 | timestamps() 11 | end 12 | 13 | @doc false 14 | def changeset(file, attrs) do 15 | file 16 | |> cast(attrs, [:path, :content]) 17 | |> validate_required([:path, :content]) 18 | |> unique_constraint(:unique_file_path, name: :unique_file_path_for_user) 19 | end 20 | end 21 | -------------------------------------------------------------------------------- /server/lib/dotsync/schemas/user.ex: -------------------------------------------------------------------------------- 1 | defmodule Dotsync.Schemas.User do 2 | use Ecto.Schema 3 | import Ecto.Changeset 4 | 5 | schema "users" do 6 | field :email, :string 7 | has_many :files, Dotsync.Schemas.File, foreign_key: :user_id 8 | 9 | timestamps() 10 | end 11 | 12 | @doc false 13 | def changeset(user, attrs) do 14 | user 15 | |> cast(attrs, [:email]) 16 | |> validate_required([:email]) 17 | |> unique_constraint(:unique_email, name: :unique_email) 18 | end 19 | end 20 | -------------------------------------------------------------------------------- /server/lib/dotsync/services/file.ex: -------------------------------------------------------------------------------- 1 | defmodule Dotsync.Services.File do 2 | alias Dotsync.Repo 3 | alias Dotsync.Schemas 4 | alias Dotsync.Jwt 5 | import Ecto.Query, only: [from: 2] 6 | 7 | def add_file(session_token, %{"path" => path, "content" => content}) do 8 | cond do 9 | path |> String.length() == 0 -> 10 | {:error, :empty_file_path} 11 | 12 | content |> String.length() == 0 -> 13 | {:error, :empty_file_content} 14 | 15 | not is_allowed_content_size?(content) -> 16 | {:error, :file_content_is_too_large} 17 | 18 | true -> 19 | session_token 20 | |> get_user_callback(fn user -> 21 | case user 22 | |> Ecto.build_assoc(:files) 23 | |> Schemas.File.changeset(%{ 24 | path: path, 25 | content: encrypt_file_content(content), 26 | user_id: user.id 27 | }) 28 | |> Repo.insert() do 29 | {:ok, _} -> {:ok, "uploaded #{path}"} 30 | {:error, _reason} -> {:error, :file_exits_for_user} 31 | end 32 | end) 33 | end 34 | end 35 | 36 | def add_directory(session_token, files) do 37 | large_files = 38 | Enum.map(files, fn %{"path" => path, "content" => content} -> 39 | if not is_allowed_content_size?(content) do 40 | path 41 | end 42 | end) 43 | |> Enum.filter(&(not is_nil(&1))) 44 | 45 | cond do 46 | is_nil(files) || length(files) == 0 -> 47 | {:error, :empty_files_list} 48 | 49 | length(large_files) != 0 -> 50 | {:error, "the files [#{large_files |> Enum.join(", ")}] are larger than 256KiB"} 51 | 52 | true -> 53 | session_token 54 | |> get_user_callback(fn user -> 55 | for %{"path" => path, "content" => content} <- files |> Enum.filter(&(not is_nil(&1))) do 56 | case user 57 | |> Ecto.build_assoc(:files) 58 | |> Schemas.File.changeset(%{ 59 | path: path, 60 | content: encrypt_file_content(content), 61 | user_id: user.id 62 | }) 63 | |> Repo.insert() do 64 | # HACK: ignoring erorrs cuz there's no problem with duplicates here! 65 | _ -> {:ok, "uploaded #{path}"} 66 | end 67 | end 68 | 69 | {:ok, "done"} 70 | end) 71 | end 72 | end 73 | 74 | def get_synced_files(session_token) do 75 | session_token 76 | |> get_user_callback(fn user -> 77 | q = 78 | from f in Schemas.File, 79 | where: f.user_id == ^user.id, 80 | select: f.path 81 | 82 | case Repo.all(q) do 83 | nil -> 84 | {:error, :no_files_were_found} 85 | 86 | [] -> 87 | {:error, :no_files_were_found} 88 | 89 | files -> 90 | {:ok, files} 91 | end 92 | end) 93 | end 94 | 95 | def delete_file(session_token, file_path) do 96 | cond do 97 | file_path |> String.length() == 0 -> 98 | {:error, :empty_file_path} 99 | 100 | true -> 101 | session_token 102 | |> get_user_callback(fn user -> 103 | file = Repo.get_by(Schemas.File, path: file_path, user_id: user.id) 104 | 105 | cond do 106 | is_nil(file) -> 107 | case delete_directory(user, file_path) do 108 | :ok -> {:ok, "deleted #{file_path}"} 109 | :error -> {:error, :file_doesnt_exits_for_user} 110 | end 111 | 112 | true -> 113 | case file 114 | |> Repo.delete() do 115 | {:ok, _} -> 116 | {:ok, "deleted #{file_path}"} 117 | 118 | {:error, _reason} -> 119 | {:error, :file_doesnt_exits_for_user} 120 | end 121 | end 122 | end) 123 | end 124 | end 125 | 126 | def delete_directory(user, dir_path) do 127 | files_to_delete = 128 | from(f in Schemas.File, 129 | where: f.user_id == ^user.id 130 | ) 131 | |> Repo.all() 132 | |> Enum.filter(fn file -> 133 | file.path |> String.starts_with?(dir_path) 134 | end) 135 | |> Enum.filter(&(not is_nil(&1))) 136 | 137 | cond do 138 | length(files_to_delete) == 0 -> 139 | {:error, :directory_is_empty_or_unsynced} 140 | 141 | true -> 142 | files_to_delete |> Enum.each(&Repo.delete(&1)) 143 | end 144 | end 145 | 146 | def download_synced_files(session_token) do 147 | session_token 148 | |> get_user_callback(fn user -> 149 | q = 150 | from f in Schemas.File, 151 | where: f.user_id == ^user.id, 152 | select: %{"path" => f.path, "content" => f.content} 153 | 154 | case Repo.all(q) do 155 | nil -> 156 | {:error, :no_files_were_found} 157 | 158 | [] -> 159 | {:error, :no_files_were_found} 160 | 161 | files -> 162 | {:ok, 163 | files 164 | |> Enum.map(fn file -> 165 | %{file | "content" => Map.get(file, "content") |> decrypt_file_content()} 166 | end)} 167 | end 168 | end) 169 | end 170 | 171 | def upload_synced_files(session_token, files) do 172 | large_files = 173 | Enum.map(files, fn %{"path" => path, "content" => content} -> 174 | if not is_allowed_content_size?(content) do 175 | path 176 | end 177 | end) 178 | |> Enum.filter(&(not is_nil(&1))) 179 | 180 | cond do 181 | is_nil(files) || length(files) == 0 -> 182 | {:error, :empty_files_list} 183 | 184 | length(large_files) != 0 -> 185 | {:error, "the files [#{large_files |> Enum.join(", ")}] are larger than 256KiB"} 186 | 187 | true -> 188 | session_token 189 | |> get_user_callback(fn user -> 190 | changed = 191 | files 192 | |> Enum.map(fn %{"path" => path, "content" => content} -> 193 | case Repo.get_by(Schemas.File, path: path, user_id: user.id) do 194 | nil -> nil 195 | file -> {file, content} 196 | end 197 | end) 198 | |> Enum.filter(&(not is_nil(&1))) 199 | |> Enum.map(fn {file, content} -> 200 | if file.content != content do 201 | file 202 | |> Ecto.Changeset.change(content: encrypt_file_content(content)) 203 | |> Repo.update() 204 | end 205 | end) 206 | |> Enum.filter(&(not is_nil(&1))) 207 | |> Enum.map(fn {_, changed} -> changed.path end) 208 | 209 | {:ok, changed} 210 | end) 211 | end 212 | end 213 | 214 | defp is_allowed_content_size?(content) do 215 | case Base.decode64(content) do 216 | {:ok, _} -> content 217 | :error -> content 218 | end 219 | |> String.length() < 256 * 1024 220 | end 221 | 222 | defp decrypt_file_content(content) do 223 | case Phoenix.Token.decrypt( 224 | DotsyncWeb.Endpoint, 225 | System.get_env("SECRET_KEY_BASE"), 226 | content 227 | ) do 228 | {:ok, content} -> content 229 | {:error, _} -> "" 230 | end 231 | end 232 | 233 | defp encrypt_file_content(content) do 234 | Phoenix.Token.encrypt( 235 | DotsyncWeb.Endpoint, 236 | System.get_env("SECRET_KEY_BASE"), 237 | content |> encode_file_content_b64() 238 | ) 239 | end 240 | 241 | defp encode_file_content_b64(content) do 242 | case Base.decode64(content) do 243 | {:ok, _} -> content 244 | :error -> Base.encode64(content) 245 | end 246 | end 247 | 248 | defp get_user_callback(token, callback) do 249 | token 250 | |> Jwt.check_callback(fn claims -> 251 | user = Repo.get_by(Schemas.User, email: Map.get(claims, "email")) 252 | 253 | cond do 254 | is_nil(user) -> {:error, :user_not_found} 255 | true -> user |> callback.() 256 | end 257 | end) 258 | end 259 | end 260 | -------------------------------------------------------------------------------- /server/lib/dotsync/services/user.ex: -------------------------------------------------------------------------------- 1 | defmodule Dotsync.Services.User do 2 | alias Dotsync.Jwt 3 | alias Dotsync.Repo 4 | alias Dotsync.Schemas 5 | alias Dotsync.Mailer 6 | 7 | @spec login_user(user_email :: String.t()) :: {:ok, any()} | {:error, atom()} 8 | def login_user(user_email) do 9 | case check_email(user_email) do 10 | true -> 11 | case create_login_token(user_email) do 12 | {:ok, token} -> 13 | [header, payload, signature] = String.split(token, ".") 14 | 15 | case Mailer.send_login_token("#{header}🔒#{signature}", user_email) do 16 | {:ok, _} -> 17 | {:ok, payload} 18 | 19 | {:error, _reason} -> 20 | {:error, :internal_error} 21 | end 22 | 23 | {:error, _reason} -> 24 | {:error, :internal_error} 25 | end 26 | 27 | false -> 28 | {:error, :invalid_email} 29 | end 30 | end 31 | 32 | @spec check_token(token :: String.t()) :: {:ok, email :: String.t()} | {:error, atom()} 33 | def check_token(token) do 34 | Jwt.check_callback(token, fn claims -> 35 | Map.get(claims, "email") |> check_or_create_user() 36 | end) 37 | end 38 | 39 | def delete_user(token) do 40 | token 41 | |> Jwt.check_callback(fn claims -> 42 | user = Repo.get_by(Schemas.User, email: Map.get(claims, "email")) 43 | 44 | cond do 45 | is_nil(user) -> {:error, :user_not_found} 46 | true -> Repo.delete(user) 47 | end 48 | end) 49 | end 50 | 51 | defp check_or_create_user(email) do 52 | {_, email} = 53 | case Schemas.User.changeset(%Schemas.User{}, %{email: email}) 54 | |> Repo.insert() do 55 | {:ok, _} -> {:ok, email} 56 | # FIXME: handle other occuring errors... 57 | {:error, _reason} -> {:ok, email} 58 | end 59 | 60 | create_session_token(email) 61 | end 62 | 63 | defp create_session_token(email) do 64 | case Jwt.generate_and_sign(%{ 65 | "email" => email, 66 | "exp" => :os.system_time(:seconds) + 30 * 24 * 60 * 60 67 | }) do 68 | {:ok, token, _claims} -> {:ok, token} 69 | {:error, _reason} -> {:error, :internal_error} 70 | end 71 | end 72 | 73 | defp create_login_token(email) do 74 | case Jwt.generate_and_sign(%{"email" => email}) do 75 | {:ok, token, _claims} -> {:ok, token} 76 | {:error, _reason} -> {:error, :internal_error} 77 | end 78 | end 79 | 80 | @type email :: charlist() 81 | defp check_email(email) do 82 | email =~ 83 | ~r/(?:[a-zA-Z\d!#$%&'*+\/=?^_{|}~-]+(?:\.[a-zA-Z\d!#$%&'*+\/=?^_{|}~-]+)*|"(?:[\x01-\x08\x0b\x0c\x0e-\x1f\x21\x23-\x5b\x5d-\x7f]|\\[\x01-\x09\x0b\x0c\x0e-\x7f])*")@(?:(?:[a-zA-Z\d](?:[a-zA-Z\d-]*[a-zA-Z\d])?\.)+[a-zA-Z\d](?:[a-zA-Z\d-]*[a-zA-Z\d])?|\[(?:(?:25[0-5]|2[0-4]\d|[01]?\d\d?)\.){3}(?:25[0-5]|2[0-4]\d|[01]?\d\d?|[a-zA-Z\d-]*[a-zA-Z\d]:(?:[\x01-\x08\x0b\x0c\x0e-\x1f\x21-\x5a\x53-\x7f]|\\[\x01-\x09\x0b\x0c\x0e-\x7f])+)])/ 84 | end 85 | end 86 | -------------------------------------------------------------------------------- /server/lib/dotsync_web.ex: -------------------------------------------------------------------------------- 1 | defmodule DotsyncWeb do 2 | @moduledoc """ 3 | The entrypoint for defining your web interface, such 4 | as controllers, components, channels, and so on. 5 | 6 | This can be used in your application as: 7 | 8 | use DotsyncWeb, :controller 9 | use DotsyncWeb, :html 10 | 11 | The definitions below will be executed for every controller, 12 | component, etc, so keep them short and clean, focused 13 | on imports, uses and aliases. 14 | 15 | Do NOT define functions inside the quoted expressions 16 | below. Instead, define additional modules and import 17 | those modules here. 18 | """ 19 | 20 | def static_paths, do: ~w(assets fonts images favicon.ico robots.txt) 21 | 22 | def router do 23 | quote do 24 | use Phoenix.Router, helpers: false 25 | 26 | # Import common connection and controller functions to use in pipelines 27 | import Plug.Conn 28 | import Phoenix.Controller 29 | end 30 | end 31 | 32 | def channel do 33 | quote do 34 | use Phoenix.Channel 35 | end 36 | end 37 | 38 | def controller do 39 | quote do 40 | use Phoenix.Controller, 41 | formats: [:html, :json], 42 | layouts: [html: DotsyncWeb.Layouts] 43 | 44 | import Plug.Conn 45 | import DotsyncWeb.Gettext 46 | 47 | unquote(verified_routes()) 48 | end 49 | end 50 | 51 | def verified_routes do 52 | quote do 53 | use Phoenix.VerifiedRoutes, 54 | endpoint: DotsyncWeb.Endpoint, 55 | router: DotsyncWeb.Router, 56 | statics: DotsyncWeb.static_paths() 57 | end 58 | end 59 | 60 | @doc """ 61 | When used, dispatch to the appropriate controller/view/etc. 62 | """ 63 | defmacro __using__(which) when is_atom(which) do 64 | apply(__MODULE__, which, []) 65 | end 66 | end 67 | -------------------------------------------------------------------------------- /server/lib/dotsync_web/controllers/error_json.ex: -------------------------------------------------------------------------------- 1 | defmodule DotsyncWeb.ErrorJSON do 2 | # If you want to customize a particular status code, 3 | # you may add your own clauses, such as: 4 | # 5 | # def render("500.json", _assigns) do 6 | # %{errors: %{detail: "Internal Server Error"}} 7 | # end 8 | 9 | # By default, Phoenix returns the status message from 10 | # the template name. For example, "404.json" becomes 11 | # "Not Found". 12 | def render(template, _assigns) do 13 | %{errors: %{detail: Phoenix.Controller.status_message_from_template(template)}} 14 | end 15 | end 16 | -------------------------------------------------------------------------------- /server/lib/dotsync_web/controllers/file.ex: -------------------------------------------------------------------------------- 1 | defmodule DotsyncWeb.File do 2 | use DotsyncWeb, :controller 3 | alias Dotsync.Services 4 | 5 | def add_file(conn, %{"path" => path, "content" => content}) do 6 | do_request_callback(conn, fn token -> 7 | Services.File.add_file(token, %{"path" => path, "content" => content}) 8 | end) 9 | end 10 | 11 | def add_directory(conn, params) do 12 | do_request_callback(conn, fn token -> 13 | Services.File.add_directory(token, Map.get(params, "files")) 14 | end) 15 | end 16 | 17 | def get_synced_files(conn, _params) do 18 | do_request_callback(conn, fn token -> 19 | Services.File.get_synced_files(token) 20 | end) 21 | end 22 | 23 | def delete_file(conn, %{"path" => file_path}) do 24 | do_request_callback(conn, fn token -> 25 | Services.File.delete_file(token, file_path) 26 | end) 27 | end 28 | 29 | def download_files(conn, _params) do 30 | do_request_callback(conn, fn token -> 31 | Services.File.download_synced_files(token) 32 | end) 33 | end 34 | 35 | def upload_files(conn, params) do 36 | do_request_callback(conn, fn token -> 37 | Services.File.upload_synced_files(token, Map.get(params, "files")) 38 | end) 39 | end 40 | 41 | defp do_request_callback(conn, callback) do 42 | case get_auth_token(conn) 43 | |> callback.() do 44 | {:ok, resp} -> 45 | r = if is_map(resp) || is_list(resp), do: resp, else: %{"msg" => resp} 46 | json(conn, r) 47 | 48 | {:error, reason} -> 49 | {status, msg} = 50 | case reason do 51 | :internal_error -> {500, reason} 52 | :invalid_token -> {401, reason} 53 | :expired_token -> {401, reason} 54 | _ -> {400, reason} 55 | end 56 | 57 | conn 58 | |> put_status(status) 59 | |> json(%{"error" => msg}) 60 | end 61 | end 62 | 63 | defp get_auth_token(conn) do 64 | case get_req_header(conn, "authorization") do 65 | [] -> "" 66 | [token] -> token 67 | end 68 | end 69 | end 70 | -------------------------------------------------------------------------------- /server/lib/dotsync_web/controllers/user.ex: -------------------------------------------------------------------------------- 1 | defmodule DotsyncWeb.User do 2 | use DotsyncWeb, :controller 3 | alias Dotsync.Services.User 4 | 5 | def login(conn, %{"email" => email}) do 6 | case User.login_user(email) do 7 | {:ok, token} -> 8 | json(conn, %{"token" => token}) 9 | 10 | {:error, reason} -> 11 | {status, msg} = 12 | case reason do 13 | :invalid_email -> {400, reason} 14 | :internal_error -> {500, reason} 15 | end 16 | 17 | conn 18 | |> put_resp_content_type("application/json") 19 | |> put_status(status) 20 | |> json(%{"error" => msg}) 21 | end 22 | end 23 | 24 | def verify_login(conn, %{"token" => token}) do 25 | case User.check_token(token) do 26 | {:ok, token} -> 27 | json(conn, %{"msg" => "ok 👍", "token" => token}) 28 | 29 | {:error, _reason} -> 30 | conn 31 | |> put_resp_content_type("application/json") 32 | |> put_status(401) 33 | |> json(%{"error" => "Invalid token!"}) 34 | end 35 | end 36 | 37 | def delete_user(conn, _params) do 38 | token = 39 | case get_req_header(conn, "authorization") do 40 | [] -> "" 41 | [token] -> token 42 | end 43 | 44 | case token |> User.delete_user() do 45 | {:ok, _} -> 46 | json(conn, %{"msg" => "ok 👍"}) 47 | 48 | {:error, _reason} -> 49 | conn 50 | |> put_resp_content_type("application/json") 51 | |> put_status(400) 52 | |> json(%{"error" => :user_not_found}) 53 | end 54 | end 55 | end 56 | -------------------------------------------------------------------------------- /server/lib/dotsync_web/endpoint.ex: -------------------------------------------------------------------------------- 1 | defmodule DotsyncWeb.Endpoint do 2 | use Phoenix.Endpoint, otp_app: :dotsync 3 | 4 | # The session will be stored in the cookie and signed, 5 | # this means its contents can be read but not tampered with. 6 | # Set :encryption_salt if you would also like to encrypt it. 7 | @session_options [ 8 | store: :cookie, 9 | key: "_dotsync_key", 10 | signing_salt: "lzEUBF3V", 11 | same_site: "Lax" 12 | ] 13 | 14 | # socket "/live", Phoenix.LiveView.Socket, websocket: [connect_info: [session: @session_options]] 15 | 16 | # Serve at "/" the static files from "priv/static" directory. 17 | # 18 | # You should set gzip to true if you are running phx.digest 19 | # when deploying your static files in production. 20 | plug Plug.Static, 21 | at: "/", 22 | from: :dotsync, 23 | gzip: false, 24 | only: DotsyncWeb.static_paths() 25 | 26 | # Code reloading can be explicitly enabled under the 27 | # :code_reloader configuration of your endpoint. 28 | if code_reloading? do 29 | plug Phoenix.CodeReloader 30 | plug Phoenix.Ecto.CheckRepoStatus, otp_app: :dotsync 31 | end 32 | 33 | plug Plug.RequestId 34 | plug Plug.Telemetry, event_prefix: [:phoenix, :endpoint] 35 | 36 | plug Plug.Parsers, 37 | parsers: [:urlencoded, :multipart, :json], 38 | pass: ["*/*"], 39 | json_decoder: Phoenix.json_library() 40 | 41 | plug Plug.MethodOverride 42 | plug Plug.Head 43 | plug Plug.Session, @session_options 44 | plug DotsyncWeb.Router 45 | end 46 | -------------------------------------------------------------------------------- /server/lib/dotsync_web/gettext.ex: -------------------------------------------------------------------------------- 1 | defmodule DotsyncWeb.Gettext do 2 | @moduledoc """ 3 | A module providing Internationalization with a gettext-based API. 4 | 5 | By using [Gettext](https://hexdocs.pm/gettext), 6 | your module gains a set of macros for translations, for example: 7 | 8 | import DotsyncWeb.Gettext 9 | 10 | # Simple translation 11 | gettext("Here is the string to translate") 12 | 13 | # Plural translation 14 | ngettext("Here is the string to translate", 15 | "Here are the strings to translate", 16 | 3) 17 | 18 | # Domain-based translation 19 | dgettext("errors", "Here is the error message to translate") 20 | 21 | See the [Gettext Docs](https://hexdocs.pm/gettext) for detailed usage. 22 | """ 23 | use Gettext, otp_app: :dotsync 24 | end 25 | -------------------------------------------------------------------------------- /server/lib/dotsync_web/router.ex: -------------------------------------------------------------------------------- 1 | defmodule DotsyncWeb.Router do 2 | use DotsyncWeb, :router 3 | 4 | pipeline :api do 5 | plug :accepts, ["json"] 6 | end 7 | 8 | scope "/user", DotsyncWeb do 9 | pipe_through :api 10 | 11 | post "/login", User, :login 12 | post "/login/verify", User, :verify_login 13 | delete "/", User, :delete_user 14 | end 15 | 16 | scope "/file", DotsyncWeb do 17 | pipe_through :api 18 | 19 | post "/", File, :add_file 20 | post "/add-directory", File, :add_directory 21 | delete "/", File, :delete_file 22 | get "/", File, :get_synced_files 23 | get "/download", File, :download_files 24 | post "/upload", File, :upload_files 25 | end 26 | end 27 | -------------------------------------------------------------------------------- /server/lib/dotsync_web/telemetry.ex: -------------------------------------------------------------------------------- 1 | defmodule DotsyncWeb.Telemetry do 2 | use Supervisor 3 | import Telemetry.Metrics 4 | 5 | def start_link(arg) do 6 | Supervisor.start_link(__MODULE__, arg, name: __MODULE__) 7 | end 8 | 9 | @impl true 10 | def init(_arg) do 11 | children = [ 12 | # Telemetry poller will execute the given period measurements 13 | # every 10_000ms. Learn more here: https://hexdocs.pm/telemetry_metrics 14 | {:telemetry_poller, measurements: periodic_measurements(), period: 10_000} 15 | # Add reporters as children of your supervision tree. 16 | # {Telemetry.Metrics.ConsoleReporter, metrics: metrics()} 17 | ] 18 | 19 | Supervisor.init(children, strategy: :one_for_one) 20 | end 21 | 22 | def metrics do 23 | [ 24 | # Phoenix Metrics 25 | summary("phoenix.endpoint.start.system_time", 26 | unit: {:native, :millisecond} 27 | ), 28 | summary("phoenix.endpoint.stop.duration", 29 | unit: {:native, :millisecond} 30 | ), 31 | summary("phoenix.router_dispatch.start.system_time", 32 | tags: [:route], 33 | unit: {:native, :millisecond} 34 | ), 35 | summary("phoenix.router_dispatch.exception.duration", 36 | tags: [:route], 37 | unit: {:native, :millisecond} 38 | ), 39 | summary("phoenix.router_dispatch.stop.duration", 40 | tags: [:route], 41 | unit: {:native, :millisecond} 42 | ), 43 | summary("phoenix.socket_connected.duration", 44 | unit: {:native, :millisecond} 45 | ), 46 | summary("phoenix.channel_joined.duration", 47 | unit: {:native, :millisecond} 48 | ), 49 | summary("phoenix.channel_handled_in.duration", 50 | tags: [:event], 51 | unit: {:native, :millisecond} 52 | ), 53 | 54 | # Database Metrics 55 | summary("dotsync.repo.query.total_time", 56 | unit: {:native, :millisecond}, 57 | description: "The sum of the other measurements" 58 | ), 59 | summary("dotsync.repo.query.decode_time", 60 | unit: {:native, :millisecond}, 61 | description: "The time spent decoding the data received from the database" 62 | ), 63 | summary("dotsync.repo.query.query_time", 64 | unit: {:native, :millisecond}, 65 | description: "The time spent executing the query" 66 | ), 67 | summary("dotsync.repo.query.queue_time", 68 | unit: {:native, :millisecond}, 69 | description: "The time spent waiting for a database connection" 70 | ), 71 | summary("dotsync.repo.query.idle_time", 72 | unit: {:native, :millisecond}, 73 | description: 74 | "The time the connection spent waiting before being checked out for the query" 75 | ), 76 | 77 | # VM Metrics 78 | summary("vm.memory.total", unit: {:byte, :kilobyte}), 79 | summary("vm.total_run_queue_lengths.total"), 80 | summary("vm.total_run_queue_lengths.cpu"), 81 | summary("vm.total_run_queue_lengths.io") 82 | ] 83 | end 84 | 85 | defp periodic_measurements do 86 | [ 87 | # A module, function and arguments to be invoked periodically. 88 | # This function must call :telemetry.execute/3 and a metric must be added above. 89 | # {DotsyncWeb, :count_users, []} 90 | ] 91 | end 92 | end 93 | -------------------------------------------------------------------------------- /server/mix.exs: -------------------------------------------------------------------------------- 1 | defmodule Dotsync.MixProject do 2 | use Mix.Project 3 | 4 | def project do 5 | [ 6 | app: :dotsync, 7 | version: "0.1.0", 8 | elixir: "~> 1.14", 9 | elixirc_paths: elixirc_paths(Mix.env()), 10 | start_permanent: Mix.env() == :prod, 11 | aliases: aliases(), 12 | deps: deps() 13 | ] 14 | end 15 | 16 | # Configuration for the OTP application. 17 | # 18 | # Type `mix help compile.app` for more information. 19 | def application do 20 | [ 21 | mod: {Dotsync.Application, []}, 22 | extra_applications: [:logger, :runtime_tools] 23 | ] 24 | end 25 | 26 | # Specifies which paths to compile per environment. 27 | defp elixirc_paths(:test), do: ["lib", "test/support"] 28 | defp elixirc_paths(_), do: ["lib"] 29 | 30 | # Specifies your project dependencies. 31 | # 32 | # Type `mix help deps` for examples and options. 33 | defp deps do 34 | [ 35 | {:phoenix, "~> 1.7.7"}, 36 | {:phoenix_ecto, "~> 4.4"}, 37 | {:ecto_sql, "~> 3.10"}, 38 | {:myxql, ">= 0.0.0"}, 39 | {:swoosh, "~> 1.3"}, 40 | {:gen_smtp, "~> 1.1"}, 41 | {:finch, "~> 0.13"}, 42 | {:telemetry_metrics, "~> 0.6"}, 43 | {:telemetry_poller, "~> 1.0"}, 44 | {:gettext, "~> 0.20"}, 45 | {:jason, "~> 1.2"}, 46 | {:plug_cowboy, "~> 2.5"}, 47 | {:joken, "~> 2.5"} 48 | ] 49 | end 50 | 51 | # Aliases are shortcuts or tasks specific to the current project. 52 | # For example, to install project dependencies and perform other setup tasks, run: 53 | # 54 | # $ mix setup 55 | # 56 | # See the documentation for `Mix` for more info on aliases. 57 | defp aliases do 58 | [ 59 | setup: ["deps.get", "ecto.setup"], 60 | "ecto.setup": ["ecto.create", "ecto.migrate", "run priv/repo/seeds.exs"], 61 | "ecto.reset": ["ecto.drop", "ecto.setup"], 62 | test: ["ecto.create --quiet", "ecto.migrate --quiet", "test"] 63 | ] 64 | end 65 | end 66 | -------------------------------------------------------------------------------- /server/mix.lock: -------------------------------------------------------------------------------- 1 | %{ 2 | "castore": {:hex, :castore, "1.0.3", "7130ba6d24c8424014194676d608cb989f62ef8039efd50ff4b3f33286d06db8", [:mix], [], "hexpm", "680ab01ef5d15b161ed6a95449fac5c6b8f60055677a8e79acf01b27baa4390b"}, 3 | "cowboy": {:hex, :cowboy, "2.10.0", "ff9ffeff91dae4ae270dd975642997afe2a1179d94b1887863e43f681a203e26", [:make, :rebar3], [{:cowlib, "2.12.1", [hex: :cowlib, repo: "hexpm", optional: false]}, {:ranch, "1.8.0", [hex: :ranch, repo: "hexpm", optional: false]}], "hexpm", "3afdccb7183cc6f143cb14d3cf51fa00e53db9ec80cdcd525482f5e99bc41d6b"}, 4 | "cowboy_telemetry": {:hex, :cowboy_telemetry, "0.4.0", "f239f68b588efa7707abce16a84d0d2acf3a0f50571f8bb7f56a15865aae820c", [:rebar3], [{:cowboy, "~> 2.7", [hex: :cowboy, repo: "hexpm", optional: false]}, {:telemetry, "~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "7d98bac1ee4565d31b62d59f8823dfd8356a169e7fcbb83831b8a5397404c9de"}, 5 | "cowlib": {:hex, :cowlib, "2.12.1", "a9fa9a625f1d2025fe6b462cb865881329b5caff8f1854d1cbc9f9533f00e1e1", [:make, :rebar3], [], "hexpm", "163b73f6367a7341b33c794c4e88e7dbfe6498ac42dcd69ef44c5bc5507c8db0"}, 6 | "db_connection": {:hex, :db_connection, "2.5.0", "bb6d4f30d35ded97b29fe80d8bd6f928a1912ca1ff110831edcd238a1973652c", [:mix], [{:telemetry, "~> 0.4 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "c92d5ba26cd69ead1ff7582dbb860adeedfff39774105a4f1c92cbb654b55aa2"}, 7 | "decimal": {:hex, :decimal, "2.1.1", "5611dca5d4b2c3dd497dec8f68751f1f1a54755e8ed2a966c2633cf885973ad6", [:mix], [], "hexpm", "53cfe5f497ed0e7771ae1a475575603d77425099ba5faef9394932b35020ffcc"}, 8 | "ecto": {:hex, :ecto, "3.10.3", "eb2ae2eecd210b4eb8bece1217b297ad4ff824b4384c0e3fdd28aaf96edd6135", [:mix], [{:decimal, "~> 1.6 or ~> 2.0", [hex: :decimal, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: true]}, {:telemetry, "~> 0.4 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "44bec74e2364d491d70f7e42cd0d690922659d329f6465e89feb8a34e8cd3433"}, 9 | "ecto_sql": {:hex, :ecto_sql, "3.10.1", "6ea6b3036a0b0ca94c2a02613fd9f742614b5cfe494c41af2e6571bb034dd94c", [:mix], [{:db_connection, "~> 2.4.1 or ~> 2.5", [hex: :db_connection, repo: "hexpm", optional: false]}, {:ecto, "~> 3.10.0", [hex: :ecto, repo: "hexpm", optional: false]}, {:myxql, "~> 0.6.0", [hex: :myxql, repo: "hexpm", optional: true]}, {:postgrex, "~> 0.16.0 or ~> 0.17.0 or ~> 1.0", [hex: :postgrex, repo: "hexpm", optional: true]}, {:tds, "~> 2.1.1 or ~> 2.2", [hex: :tds, repo: "hexpm", optional: true]}, {:telemetry, "~> 0.4.0 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "f6a25bdbbd695f12c8171eaff0851fa4c8e72eec1e98c7364402dda9ce11c56b"}, 10 | "expo": {:hex, :expo, "0.4.1", "1c61d18a5df197dfda38861673d392e642649a9cef7694d2f97a587b2cfb319b", [:mix], [], "hexpm", "2ff7ba7a798c8c543c12550fa0e2cbc81b95d4974c65855d8d15ba7b37a1ce47"}, 11 | "finch": {:hex, :finch, "0.16.0", "40733f02c89f94a112518071c0a91fe86069560f5dbdb39f9150042f44dcfb1a", [:mix], [{:castore, "~> 0.1 or ~> 1.0", [hex: :castore, repo: "hexpm", optional: false]}, {:mime, "~> 1.0 or ~> 2.0", [hex: :mime, repo: "hexpm", optional: false]}, {:mint, "~> 1.3", [hex: :mint, repo: "hexpm", optional: false]}, {:nimble_options, "~> 0.4 or ~> 1.0", [hex: :nimble_options, repo: "hexpm", optional: false]}, {:nimble_pool, "~> 0.2.6 or ~> 1.0", [hex: :nimble_pool, repo: "hexpm", optional: false]}, {:telemetry, "~> 0.4 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "f660174c4d519e5fec629016054d60edd822cdfe2b7270836739ac2f97735ec5"}, 12 | "gen_smtp": {:hex, :gen_smtp, "1.2.0", "9cfc75c72a8821588b9b9fe947ae5ab2aed95a052b81237e0928633a13276fd3", [:rebar3], [{:ranch, ">= 1.8.0", [hex: :ranch, repo: "hexpm", optional: false]}], "hexpm", "5ee0375680bca8f20c4d85f58c2894441443a743355430ff33a783fe03296779"}, 13 | "gettext": {:hex, :gettext, "0.22.3", "c8273e78db4a0bb6fba7e9f0fd881112f349a3117f7f7c598fa18c66c888e524", [:mix], [{:expo, "~> 0.4.0", [hex: :expo, repo: "hexpm", optional: false]}], "hexpm", "935f23447713954a6866f1bb28c3a878c4c011e802bcd68a726f5e558e4b64bd"}, 14 | "hpax": {:hex, :hpax, "0.1.2", "09a75600d9d8bbd064cdd741f21fc06fc1f4cf3d0fcc335e5aa19be1a7235c84", [:mix], [], "hexpm", "2c87843d5a23f5f16748ebe77969880e29809580efdaccd615cd3bed628a8c13"}, 15 | "jason": {:hex, :jason, "1.4.1", "af1504e35f629ddcdd6addb3513c3853991f694921b1b9368b0bd32beb9f1b63", [:mix], [{:decimal, "~> 1.0 or ~> 2.0", [hex: :decimal, repo: "hexpm", optional: true]}], "hexpm", "fbb01ecdfd565b56261302f7e1fcc27c4fb8f32d56eab74db621fc154604a7a1"}, 16 | "joken": {:hex, :joken, "2.6.0", "b9dd9b6d52e3e6fcb6c65e151ad38bf4bc286382b5b6f97079c47ade6b1bcc6a", [:mix], [{:jose, "~> 1.11.5", [hex: :jose, repo: "hexpm", optional: false]}], "hexpm", "5a95b05a71cd0b54abd35378aeb1d487a23a52c324fa7efdffc512b655b5aaa7"}, 17 | "jose": {:hex, :jose, "1.11.6", "613fda82552128aa6fb804682e3a616f4bc15565a048dabd05b1ebd5827ed965", [:mix, :rebar3], [], "hexpm", "6275cb75504f9c1e60eeacb771adfeee4905a9e182103aa59b53fed651ff9738"}, 18 | "mail": {:hex, :mail, "0.3.1", "cb0a14e4ed8904e4e5a08214e686ccf6f9099346885db17d8c309381f865cc5c", [:mix], [], "hexpm", "1db701e89865c1d5fa296b2b57b1cd587587cca8d8a1a22892b35ef5a8e352a6"}, 19 | "mime": {:hex, :mime, "2.0.5", "dc34c8efd439abe6ae0343edbb8556f4d63f178594894720607772a041b04b02", [:mix], [], "hexpm", "da0d64a365c45bc9935cc5c8a7fc5e49a0e0f9932a761c55d6c52b142780a05c"}, 20 | "mint": {:hex, :mint, "1.5.1", "8db5239e56738552d85af398798c80648db0e90f343c8469f6c6d8898944fb6f", [:mix], [{:castore, "~> 0.1.0 or ~> 1.0", [hex: :castore, repo: "hexpm", optional: true]}, {:hpax, "~> 0.1.1", [hex: :hpax, repo: "hexpm", optional: false]}], "hexpm", "4a63e1e76a7c3956abd2c72f370a0d0aecddc3976dea5c27eccbecfa5e7d5b1e"}, 21 | "myxql": {:hex, :myxql, "0.6.3", "3d77683a09f1227abb8b73d66b275262235c5cae68182f0cfa5897d72a03700e", [:mix], [{:db_connection, "~> 2.4.1 or ~> 2.5", [hex: :db_connection, repo: "hexpm", optional: false]}, {:decimal, "~> 1.6 or ~> 2.0", [hex: :decimal, repo: "hexpm", optional: false]}, {:geo, "~> 3.4", [hex: :geo, repo: "hexpm", optional: true]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: true]}, {:table, "~> 0.1.0", [hex: :table, repo: "hexpm", optional: true]}], "hexpm", "af9eb517ddaced5c5c28e8749015493757fd4413f2cfccea449c466d405d9f51"}, 22 | "nimble_options": {:hex, :nimble_options, "1.0.2", "92098a74df0072ff37d0c12ace58574d26880e522c22801437151a159392270e", [:mix], [], "hexpm", "fd12a8db2021036ce12a309f26f564ec367373265b53e25403f0ee697380f1b8"}, 23 | "nimble_pool": {:hex, :nimble_pool, "1.0.0", "5eb82705d138f4dd4423f69ceb19ac667b3b492ae570c9f5c900bb3d2f50a847", [:mix], [], "hexpm", "80be3b882d2d351882256087078e1b1952a28bf98d0a287be87e4a24a710b67a"}, 24 | "phoenix": {:hex, :phoenix, "1.7.7", "4cc501d4d823015007ba3cdd9c41ecaaf2ffb619d6fb283199fa8ddba89191e0", [:mix], [{:castore, ">= 0.0.0", [hex: :castore, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: true]}, {:phoenix_pubsub, "~> 2.1", [hex: :phoenix_pubsub, repo: "hexpm", optional: false]}, {:phoenix_template, "~> 1.0", [hex: :phoenix_template, repo: "hexpm", optional: false]}, {:phoenix_view, "~> 2.0", [hex: :phoenix_view, repo: "hexpm", optional: true]}, {:plug, "~> 1.14", [hex: :plug, repo: "hexpm", optional: false]}, {:plug_cowboy, "~> 2.6", [hex: :plug_cowboy, repo: "hexpm", optional: true]}, {:plug_crypto, "~> 1.2", [hex: :plug_crypto, repo: "hexpm", optional: false]}, {:telemetry, "~> 0.4 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}, {:websock_adapter, "~> 0.5.3", [hex: :websock_adapter, repo: "hexpm", optional: false]}], "hexpm", "8966e15c395e5e37591b6ed0bd2ae7f48e961f0f60ac4c733f9566b519453085"}, 25 | "phoenix_ecto": {:hex, :phoenix_ecto, "4.4.2", "b21bd01fdeffcfe2fab49e4942aa938b6d3e89e93a480d4aee58085560a0bc0d", [:mix], [{:ecto, "~> 3.5", [hex: :ecto, repo: "hexpm", optional: false]}, {:phoenix_html, "~> 2.14.2 or ~> 3.0", [hex: :phoenix_html, repo: "hexpm", optional: true]}, {:plug, "~> 1.9", [hex: :plug, repo: "hexpm", optional: false]}], "hexpm", "70242edd4601d50b69273b057ecf7b684644c19ee750989fd555625ae4ce8f5d"}, 26 | "phoenix_pubsub": {:hex, :phoenix_pubsub, "2.1.3", "3168d78ba41835aecad272d5e8cd51aa87a7ac9eb836eabc42f6e57538e3731d", [:mix], [], "hexpm", "bba06bc1dcfd8cb086759f0edc94a8ba2bc8896d5331a1e2c2902bf8e36ee502"}, 27 | "phoenix_template": {:hex, :phoenix_template, "1.0.3", "32de561eefcefa951aead30a1f94f1b5f0379bc9e340bb5c667f65f1edfa4326", [:mix], [{:phoenix_html, "~> 2.14.2 or ~> 3.0", [hex: :phoenix_html, repo: "hexpm", optional: true]}], "hexpm", "16f4b6588a4152f3cc057b9d0c0ba7e82ee23afa65543da535313ad8d25d8e2c"}, 28 | "plug": {:hex, :plug, "1.14.2", "cff7d4ec45b4ae176a227acd94a7ab536d9b37b942c8e8fa6dfc0fff98ff4d80", [:mix], [{:mime, "~> 1.0 or ~> 2.0", [hex: :mime, repo: "hexpm", optional: false]}, {:plug_crypto, "~> 1.1.1 or ~> 1.2", [hex: :plug_crypto, repo: "hexpm", optional: false]}, {:telemetry, "~> 0.4.3 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "842fc50187e13cf4ac3b253d47d9474ed6c296a8732752835ce4a86acdf68d13"}, 29 | "plug_cowboy": {:hex, :plug_cowboy, "2.6.1", "9a3bbfceeb65eff5f39dab529e5cd79137ac36e913c02067dba3963a26efe9b2", [:mix], [{:cowboy, "~> 2.7", [hex: :cowboy, repo: "hexpm", optional: false]}, {:cowboy_telemetry, "~> 0.3", [hex: :cowboy_telemetry, repo: "hexpm", optional: false]}, {:plug, "~> 1.14", [hex: :plug, repo: "hexpm", optional: false]}], "hexpm", "de36e1a21f451a18b790f37765db198075c25875c64834bcc82d90b309eb6613"}, 30 | "plug_crypto": {:hex, :plug_crypto, "1.2.5", "918772575e48e81e455818229bf719d4ab4181fcbf7f85b68a35620f78d89ced", [:mix], [], "hexpm", "26549a1d6345e2172eb1c233866756ae44a9609bd33ee6f99147ab3fd87fd842"}, 31 | "ranch": {:hex, :ranch, "1.8.0", "8c7a100a139fd57f17327b6413e4167ac559fbc04ca7448e9be9057311597a1d", [:make, :rebar3], [], "hexpm", "49fbcfd3682fab1f5d109351b61257676da1a2fdbe295904176d5e521a2ddfe5"}, 32 | "ssl_verify_fun": {:hex, :ssl_verify_fun, "1.1.7", "354c321cf377240c7b8716899e182ce4890c5938111a1296add3ec74cf1715df", [:make, :mix, :rebar3], [], "hexpm", "fe4c190e8f37401d30167c8c405eda19469f34577987c76dde613e838bbc67f8"}, 33 | "swoosh": {:hex, :swoosh, "1.11.4", "9b353f998cba3c5e101a0669559c2fb2757b5d9eb7db058bf08687d82e93e416", [:mix], [{:cowboy, "~> 1.1 or ~> 2.4", [hex: :cowboy, repo: "hexpm", optional: true]}, {:ex_aws, "~> 2.1", [hex: :ex_aws, repo: "hexpm", optional: true]}, {:finch, "~> 0.6", [hex: :finch, repo: "hexpm", optional: true]}, {:gen_smtp, "~> 0.13 or ~> 1.0", [hex: :gen_smtp, repo: "hexpm", optional: true]}, {:hackney, "~> 1.9", [hex: :hackney, repo: "hexpm", optional: true]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: false]}, {:mail, "~> 0.2", [hex: :mail, repo: "hexpm", optional: true]}, {:mime, "~> 1.1 or ~> 2.0", [hex: :mime, repo: "hexpm", optional: false]}, {:plug_cowboy, ">= 1.0.0", [hex: :plug_cowboy, repo: "hexpm", optional: true]}, {:telemetry, "~> 0.4.2 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "d3390914022a456ae1604bfcb3431bd12509b2afe8c70296bae6c9dca4903d0f"}, 34 | "telemetry": {:hex, :telemetry, "1.2.1", "68fdfe8d8f05a8428483a97d7aab2f268aaff24b49e0f599faa091f1d4e7f61c", [:rebar3], [], "hexpm", "dad9ce9d8effc621708f99eac538ef1cbe05d6a874dd741de2e689c47feafed5"}, 35 | "telemetry_metrics": {:hex, :telemetry_metrics, "0.6.1", "315d9163a1d4660aedc3fee73f33f1d355dcc76c5c3ab3d59e76e3edf80eef1f", [:mix], [{:telemetry, "~> 0.4 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "7be9e0871c41732c233be71e4be11b96e56177bf15dde64a8ac9ce72ac9834c6"}, 36 | "telemetry_poller": {:hex, :telemetry_poller, "1.0.0", "db91bb424e07f2bb6e73926fcafbfcbcb295f0193e0a00e825e589a0a47e8453", [:rebar3], [{:telemetry, "~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "b3a24eafd66c3f42da30fc3ca7dda1e9d546c12250a2d60d7b81d264fbec4f6e"}, 37 | "tls_certificate_check": {:hex, :tls_certificate_check, "1.19.0", "c76c4c5d79ee79a2b11c84f910c825d6f024a78427c854f515748e9bd025e987", [:rebar3], [{:ssl_verify_fun, "~> 1.1", [hex: :ssl_verify_fun, repo: "hexpm", optional: false]}], "hexpm", "4083b4a298add534c96125337cb01161c358bb32dd870d5a893aae685fd91d70"}, 38 | "websock": {:hex, :websock, "0.5.2", "b3c08511d8d79ed2c2f589ff430bd1fe799bb389686dafce86d28801783d8351", [:mix], [], "hexpm", "925f5de22fca6813dfa980fb62fd542ec43a2d1a1f83d2caec907483fe66ff05"}, 39 | "websock_adapter": {:hex, :websock_adapter, "0.5.3", "4908718e42e4a548fc20e00e70848620a92f11f7a6add8cf0886c4232267498d", [:mix], [{:bandit, ">= 0.6.0", [hex: :bandit, repo: "hexpm", optional: true]}, {:plug, "~> 1.14", [hex: :plug, repo: "hexpm", optional: false]}, {:plug_cowboy, "~> 2.6", [hex: :plug_cowboy, repo: "hexpm", optional: true]}, {:websock, "~> 0.5", [hex: :websock, repo: "hexpm", optional: false]}], "hexpm", "cbe5b814c1f86b6ea002b52dd99f345aeecf1a1a6964e209d208fb404d930d3d"}, 40 | } 41 | -------------------------------------------------------------------------------- /server/priv/gettext/en/LC_MESSAGES/errors.po: -------------------------------------------------------------------------------- 1 | ## `msgid`s in this file come from POT (.pot) files. 2 | ## 3 | ## Do not add, change, or remove `msgid`s manually here as 4 | ## they're tied to the ones in the corresponding POT file 5 | ## (with the same domain). 6 | ## 7 | ## Use `mix gettext.extract --merge` or `mix gettext.merge` 8 | ## to merge POT files into PO files. 9 | msgid "" 10 | msgstr "" 11 | "Language: en\n" 12 | 13 | ## From Ecto.Changeset.cast/4 14 | msgid "can't be blank" 15 | msgstr "" 16 | 17 | ## From Ecto.Changeset.unique_constraint/3 18 | msgid "has already been taken" 19 | msgstr "" 20 | 21 | ## From Ecto.Changeset.put_change/3 22 | msgid "is invalid" 23 | msgstr "" 24 | 25 | ## From Ecto.Changeset.validate_acceptance/3 26 | msgid "must be accepted" 27 | msgstr "" 28 | 29 | ## From Ecto.Changeset.validate_format/3 30 | msgid "has invalid format" 31 | msgstr "" 32 | 33 | ## From Ecto.Changeset.validate_subset/3 34 | msgid "has an invalid entry" 35 | msgstr "" 36 | 37 | ## From Ecto.Changeset.validate_exclusion/3 38 | msgid "is reserved" 39 | msgstr "" 40 | 41 | ## From Ecto.Changeset.validate_confirmation/3 42 | msgid "does not match confirmation" 43 | msgstr "" 44 | 45 | ## From Ecto.Changeset.no_assoc_constraint/3 46 | msgid "is still associated with this entry" 47 | msgstr "" 48 | 49 | msgid "are still associated with this entry" 50 | msgstr "" 51 | 52 | ## From Ecto.Changeset.validate_length/3 53 | msgid "should have %{count} item(s)" 54 | msgid_plural "should have %{count} item(s)" 55 | msgstr[0] "" 56 | msgstr[1] "" 57 | 58 | msgid "should be %{count} character(s)" 59 | msgid_plural "should be %{count} character(s)" 60 | msgstr[0] "" 61 | msgstr[1] "" 62 | 63 | msgid "should be %{count} byte(s)" 64 | msgid_plural "should be %{count} byte(s)" 65 | msgstr[0] "" 66 | msgstr[1] "" 67 | 68 | msgid "should have at least %{count} item(s)" 69 | msgid_plural "should have at least %{count} item(s)" 70 | msgstr[0] "" 71 | msgstr[1] "" 72 | 73 | msgid "should be at least %{count} character(s)" 74 | msgid_plural "should be at least %{count} character(s)" 75 | msgstr[0] "" 76 | msgstr[1] "" 77 | 78 | msgid "should be at least %{count} byte(s)" 79 | msgid_plural "should be at least %{count} byte(s)" 80 | msgstr[0] "" 81 | msgstr[1] "" 82 | 83 | msgid "should have at most %{count} item(s)" 84 | msgid_plural "should have at most %{count} item(s)" 85 | msgstr[0] "" 86 | msgstr[1] "" 87 | 88 | msgid "should be at most %{count} character(s)" 89 | msgid_plural "should be at most %{count} character(s)" 90 | msgstr[0] "" 91 | msgstr[1] "" 92 | 93 | msgid "should be at most %{count} byte(s)" 94 | msgid_plural "should be at most %{count} byte(s)" 95 | msgstr[0] "" 96 | msgstr[1] "" 97 | 98 | ## From Ecto.Changeset.validate_number/3 99 | msgid "must be less than %{number}" 100 | msgstr "" 101 | 102 | msgid "must be greater than %{number}" 103 | msgstr "" 104 | 105 | msgid "must be less than or equal to %{number}" 106 | msgstr "" 107 | 108 | msgid "must be greater than or equal to %{number}" 109 | msgstr "" 110 | 111 | msgid "must be equal to %{number}" 112 | msgstr "" 113 | -------------------------------------------------------------------------------- /server/priv/gettext/errors.pot: -------------------------------------------------------------------------------- 1 | ## This is a PO Template file. 2 | ## 3 | ## `msgid`s here are often extracted from source code. 4 | ## Add new translations manually only if they're dynamic 5 | ## translations that can't be statically extracted. 6 | ## 7 | ## Run `mix gettext.extract` to bring this file up to 8 | ## date. Leave `msgstr`s empty as changing them here has no 9 | ## effect: edit them in PO (`.po`) files instead. 10 | 11 | ## From Ecto.Changeset.cast/4 12 | msgid "can't be blank" 13 | msgstr "" 14 | 15 | ## From Ecto.Changeset.unique_constraint/3 16 | msgid "has already been taken" 17 | msgstr "" 18 | 19 | ## From Ecto.Changeset.put_change/3 20 | msgid "is invalid" 21 | msgstr "" 22 | 23 | ## From Ecto.Changeset.validate_acceptance/3 24 | msgid "must be accepted" 25 | msgstr "" 26 | 27 | ## From Ecto.Changeset.validate_format/3 28 | msgid "has invalid format" 29 | msgstr "" 30 | 31 | ## From Ecto.Changeset.validate_subset/3 32 | msgid "has an invalid entry" 33 | msgstr "" 34 | 35 | ## From Ecto.Changeset.validate_exclusion/3 36 | msgid "is reserved" 37 | msgstr "" 38 | 39 | ## From Ecto.Changeset.validate_confirmation/3 40 | msgid "does not match confirmation" 41 | msgstr "" 42 | 43 | ## From Ecto.Changeset.no_assoc_constraint/3 44 | msgid "is still associated with this entry" 45 | msgstr "" 46 | 47 | msgid "are still associated with this entry" 48 | msgstr "" 49 | 50 | ## From Ecto.Changeset.validate_length/3 51 | msgid "should have %{count} item(s)" 52 | msgid_plural "should have %{count} item(s)" 53 | msgstr[0] "" 54 | msgstr[1] "" 55 | 56 | msgid "should be %{count} character(s)" 57 | msgid_plural "should be %{count} character(s)" 58 | msgstr[0] "" 59 | msgstr[1] "" 60 | 61 | msgid "should be %{count} byte(s)" 62 | msgid_plural "should be %{count} byte(s)" 63 | msgstr[0] "" 64 | msgstr[1] "" 65 | 66 | msgid "should have at least %{count} item(s)" 67 | msgid_plural "should have at least %{count} item(s)" 68 | msgstr[0] "" 69 | msgstr[1] "" 70 | 71 | msgid "should be at least %{count} character(s)" 72 | msgid_plural "should be at least %{count} character(s)" 73 | msgstr[0] "" 74 | msgstr[1] "" 75 | 76 | msgid "should be at least %{count} byte(s)" 77 | msgid_plural "should be at least %{count} byte(s)" 78 | msgstr[0] "" 79 | msgstr[1] "" 80 | 81 | msgid "should have at most %{count} item(s)" 82 | msgid_plural "should have at most %{count} item(s)" 83 | msgstr[0] "" 84 | msgstr[1] "" 85 | 86 | msgid "should be at most %{count} character(s)" 87 | msgid_plural "should be at most %{count} character(s)" 88 | msgstr[0] "" 89 | msgstr[1] "" 90 | 91 | msgid "should be at most %{count} byte(s)" 92 | msgid_plural "should be at most %{count} byte(s)" 93 | msgstr[0] "" 94 | msgstr[1] "" 95 | 96 | ## From Ecto.Changeset.validate_number/3 97 | msgid "must be less than %{number}" 98 | msgstr "" 99 | 100 | msgid "must be greater than %{number}" 101 | msgstr "" 102 | 103 | msgid "must be less than or equal to %{number}" 104 | msgstr "" 105 | 106 | msgid "must be greater than or equal to %{number}" 107 | msgstr "" 108 | 109 | msgid "must be equal to %{number}" 110 | msgstr "" 111 | -------------------------------------------------------------------------------- /server/priv/repo/migrations/.formatter.exs: -------------------------------------------------------------------------------- 1 | [ 2 | import_deps: [:ecto_sql], 3 | inputs: ["*.exs"] 4 | ] 5 | -------------------------------------------------------------------------------- /server/priv/repo/migrations/20230817134607_initial.exs: -------------------------------------------------------------------------------- 1 | defmodule Dotsync.Repo.Migrations.Initial do 2 | use Ecto.Migration 3 | 4 | def change do 5 | create table("users") do 6 | add :email, :string 7 | 8 | timestamps() 9 | end 10 | 11 | create unique_index(:users, [:email], name: :unique_email) 12 | 13 | create table("files") do 14 | add :path, :string 15 | add :content, :string 16 | add :user_id, references(:users, on_delete: :delete_all) 17 | 18 | timestamps() 19 | end 20 | 21 | create unique_index(:files, [:path, :user_id], name: :unique_file_path_for_user) 22 | end 23 | end 24 | -------------------------------------------------------------------------------- /server/priv/repo/migrations/20230820223642_increase_file_content_length.exs: -------------------------------------------------------------------------------- 1 | defmodule Dotsync.Repo.Migrations.IncreaseFileContentLength do 2 | use Ecto.Migration 3 | 4 | def change do 5 | alter table("files") do 6 | modify :content, :text 7 | end 8 | end 9 | end 10 | -------------------------------------------------------------------------------- /server/priv/repo/migrations/20230823091359_increase_file_content_length_again.exs: -------------------------------------------------------------------------------- 1 | defmodule Dotsync.Repo.Migrations.IncreaseFileContentLengthAgain do 2 | use Ecto.Migration 3 | 4 | def change do 5 | alter table("files") do 6 | modify :content, :mediumtext 7 | end 8 | end 9 | end 10 | -------------------------------------------------------------------------------- /server/priv/repo/seeds.exs: -------------------------------------------------------------------------------- 1 | # Script for populating the database. You can run it as: 2 | # 3 | # mix run priv/repo/seeds.exs 4 | # 5 | # Inside the script, you can read and write to any of your 6 | # repositories directly: 7 | # 8 | # Dotsync.Repo.insert!(%Dotsync.SomeSchema{}) 9 | # 10 | # We recommend using the bang functions (`insert!`, `update!` 11 | # and so on) as they will fail if something goes wrong. 12 | -------------------------------------------------------------------------------- /server/priv/static/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mbaraa/dotsync/e6c2f5eed6bcaad12f893ff735c75ec7f97f2183/server/priv/static/favicon.ico -------------------------------------------------------------------------------- /server/priv/static/robots.txt: -------------------------------------------------------------------------------- 1 | # See https://www.robotstxt.org/robotstxt.html for documentation on how to use the robots.txt file 2 | # 3 | # To ban all spiders from the entire site uncomment the next two lines: 4 | # User-agent: * 5 | # Disallow: / 6 | -------------------------------------------------------------------------------- /server/test/dotsync_web/controllers/error_json_test.exs: -------------------------------------------------------------------------------- 1 | defmodule DotsyncWeb.ErrorJSONTest do 2 | use DotsyncWeb.ConnCase, async: true 3 | 4 | test "renders 404" do 5 | assert DotsyncWeb.ErrorJSON.render("404.json", %{}) == %{errors: %{detail: "Not Found"}} 6 | end 7 | 8 | test "renders 500" do 9 | assert DotsyncWeb.ErrorJSON.render("500.json", %{}) == 10 | %{errors: %{detail: "Internal Server Error"}} 11 | end 12 | end 13 | -------------------------------------------------------------------------------- /server/test/support/conn_case.ex: -------------------------------------------------------------------------------- 1 | defmodule DotsyncWeb.ConnCase do 2 | @moduledoc """ 3 | This module defines the test case to be used by 4 | tests that require setting up a connection. 5 | 6 | Such tests rely on `Phoenix.ConnTest` and also 7 | import other functionality to make it easier 8 | to build common data structures and query the data layer. 9 | 10 | Finally, if the test case interacts with the database, 11 | we enable the SQL sandbox, so changes done to the database 12 | are reverted at the end of every test. If you are using 13 | PostgreSQL, you can even run database tests asynchronously 14 | by setting `use DotsyncWeb.ConnCase, async: true`, although 15 | this option is not recommended for other databases. 16 | """ 17 | 18 | use ExUnit.CaseTemplate 19 | 20 | using do 21 | quote do 22 | # The default endpoint for testing 23 | @endpoint DotsyncWeb.Endpoint 24 | 25 | use DotsyncWeb, :verified_routes 26 | 27 | # Import conveniences for testing with connections 28 | import Plug.Conn 29 | import Phoenix.ConnTest 30 | import DotsyncWeb.ConnCase 31 | end 32 | end 33 | 34 | setup tags do 35 | Dotsync.DataCase.setup_sandbox(tags) 36 | {:ok, conn: Phoenix.ConnTest.build_conn()} 37 | end 38 | end 39 | -------------------------------------------------------------------------------- /server/test/support/data_case.ex: -------------------------------------------------------------------------------- 1 | defmodule Dotsync.DataCase do 2 | @moduledoc """ 3 | This module defines the setup for tests requiring 4 | access to the application's data layer. 5 | 6 | You may define functions here to be used as helpers in 7 | your tests. 8 | 9 | Finally, if the test case interacts with the database, 10 | we enable the SQL sandbox, so changes done to the database 11 | are reverted at the end of every test. If you are using 12 | PostgreSQL, you can even run database tests asynchronously 13 | by setting `use Dotsync.DataCase, async: true`, although 14 | this option is not recommended for other databases. 15 | """ 16 | 17 | use ExUnit.CaseTemplate 18 | 19 | using do 20 | quote do 21 | alias Dotsync.Repo 22 | 23 | import Ecto 24 | import Ecto.Changeset 25 | import Ecto.Query 26 | import Dotsync.DataCase 27 | end 28 | end 29 | 30 | setup tags do 31 | Dotsync.DataCase.setup_sandbox(tags) 32 | :ok 33 | end 34 | 35 | @doc """ 36 | Sets up the sandbox based on the test tags. 37 | """ 38 | def setup_sandbox(tags) do 39 | pid = Ecto.Adapters.SQL.Sandbox.start_owner!(Dotsync.Repo, shared: not tags[:async]) 40 | on_exit(fn -> Ecto.Adapters.SQL.Sandbox.stop_owner(pid) end) 41 | end 42 | 43 | @doc """ 44 | A helper that transforms changeset errors into a map of messages. 45 | 46 | assert {:error, changeset} = Accounts.create_user(%{password: "short"}) 47 | assert "password is too short" in errors_on(changeset).password 48 | assert %{password: ["password is too short"]} = errors_on(changeset) 49 | 50 | """ 51 | def errors_on(changeset) do 52 | Ecto.Changeset.traverse_errors(changeset, fn {message, opts} -> 53 | Regex.replace(~r"%{(\w+)}", message, fn _, key -> 54 | opts |> Keyword.get(String.to_existing_atom(key), key) |> to_string() 55 | end) 56 | end) 57 | end 58 | end 59 | -------------------------------------------------------------------------------- /server/test/test_helper.exs: -------------------------------------------------------------------------------- 1 | ExUnit.start() 2 | Ecto.Adapters.SQL.Sandbox.mode(Dotsync.Repo, :manual) 3 | -------------------------------------------------------------------------------- /utils/configfile/configfile.go: -------------------------------------------------------------------------------- 1 | package configfile 2 | 3 | import ( 4 | "errors" 5 | "fmt" 6 | "io" 7 | "os" 8 | "strings" 9 | 10 | "github.com/mbaraa/dotsync/config" 11 | ) 12 | 13 | func GetValue(key string) (string, error) { 14 | values, err := readFile() 15 | if err != nil { 16 | return "", err 17 | } 18 | 19 | if value, ok := values[key]; ok { 20 | return value, nil 21 | } 22 | 23 | return "", errors.New("key was not found") 24 | } 25 | 26 | func SetValue(key, value string) error { 27 | values, err := readFile() 28 | if err != nil { 29 | return err 30 | } 31 | values[key] = value 32 | 33 | return writeToFile(values) 34 | } 35 | 36 | func readFile() (map[string]string, error) { 37 | file, err := openFile() 38 | if err != nil { 39 | return nil, err 40 | } 41 | defer file.Close() 42 | 43 | buf, err := io.ReadAll(file) 44 | if err != nil { 45 | return nil, err 46 | } 47 | 48 | values := make(map[string]string) 49 | lines := strings.Split(string(buf), "\n") 50 | for _, line := range lines { 51 | if !strings.Contains(line, "=") { 52 | continue 53 | } 54 | keyValue := strings.Split(line, "=") 55 | values[keyValue[0]] = keyValue[1] 56 | } 57 | 58 | return values, nil 59 | } 60 | 61 | func writeToFile(values map[string]string) error { 62 | file, err := openFile() 63 | if err != nil { 64 | return err 65 | } 66 | defer file.Close() 67 | 68 | for key, value := range values { 69 | if len(key) == 0 || len(value) == 0 { 70 | continue 71 | } 72 | fmt.Fprintf(file, "%s=%s\n", key, value) 73 | } 74 | 75 | return nil 76 | } 77 | 78 | func openFile() (file *os.File, err error) { 79 | file, err = os.OpenFile(config.ConfigFilePath, os.O_RDWR, 0755) 80 | if errors.Is(err, os.ErrNotExist) { 81 | file, err = os.Create(config.ConfigFilePath) 82 | if err != nil { 83 | return 84 | } 85 | } 86 | 87 | return 88 | } 89 | -------------------------------------------------------------------------------- /utils/json/json.go: -------------------------------------------------------------------------------- 1 | package json 2 | 3 | import ( 4 | "bytes" 5 | "encoding/json" 6 | "io" 7 | "strings" 8 | ) 9 | 10 | // Json well it's a json. 11 | type Json = map[string]any 12 | 13 | // Parse decodes the given json string into its corresponding type. 14 | func Parse[T any](s string) (T, error) { 15 | buf := strings.NewReader(s) 16 | return ParseFromReader[T](buf) 17 | } 18 | 19 | // ParseFromReader decodes the given json string from an io.Reader into its corresponding type. 20 | func ParseFromReader[T any](reader io.Reader) (T, error) { 21 | dec := json.NewDecoder(reader) 22 | 23 | var target T 24 | 25 | err := dec.Decode(&target) 26 | if err != nil { 27 | return target, err 28 | } 29 | 30 | return target, nil 31 | } 32 | 33 | // Stringify encodes the given object into json string. 34 | func Stringify[T any](obj T) (string, error) { 35 | out := bytes.NewBuffer([]byte{}) 36 | err := StringifyToWriter(out, obj) 37 | if err != nil { 38 | return "", err 39 | } 40 | return out.String(), nil 41 | } 42 | 43 | // Stringify encodes the given object into a json string to the given io.Writer. 44 | func StringifyToWriter[T any](writer io.Writer, obj T) error { 45 | enc := json.NewEncoder(writer) 46 | 47 | err := enc.Encode(obj) 48 | if err != nil { 49 | return err 50 | } 51 | 52 | return nil 53 | } 54 | 55 | func Jsonify[T any](obj T) (Json, error) { 56 | jsonStr, err := Stringify(obj) 57 | if err != nil { 58 | return nil, err 59 | } 60 | mappedJson, err := Parse[Json](jsonStr) 61 | if err != nil { 62 | return nil, err 63 | } 64 | return mappedJson, nil 65 | } 66 | -------------------------------------------------------------------------------- /website/.dockerignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | node_modules 3 | /build 4 | /.svelte-kit 5 | /package 6 | vite.config.js.timestamp-* 7 | vite.config.ts.timestamp-* 8 | -------------------------------------------------------------------------------- /website/.env.example: -------------------------------------------------------------------------------- 1 | GITHUB_TOKEN= 2 | -------------------------------------------------------------------------------- /website/.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | node_modules 3 | /build 4 | /.svelte-kit 5 | /package 6 | .env 7 | .env.* 8 | !.env.example 9 | vite.config.js.timestamp-* 10 | vite.config.ts.timestamp-* 11 | -------------------------------------------------------------------------------- /website/.npmrc: -------------------------------------------------------------------------------- 1 | engine-strict=true 2 | resolution-mode=highest 3 | -------------------------------------------------------------------------------- /website/.prettierignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | node_modules 3 | /build 4 | /.svelte-kit 5 | /package 6 | .env 7 | .env.* 8 | !.env.example 9 | 10 | # Ignore files for PNPM, NPM and YARN 11 | pnpm-lock.yaml 12 | package-lock.json 13 | yarn.lock 14 | -------------------------------------------------------------------------------- /website/.prettierrc: -------------------------------------------------------------------------------- 1 | { 2 | "useTabs": true, 3 | "singleQuote": false, 4 | "trailingComma": "none", 5 | "printWidth": 100, 6 | "plugins": ["prettier-plugin-svelte"], 7 | "pluginSearchDirs": ["."], 8 | "overrides": [{ "files": "*.svelte", "options": { "parser": "svelte" } }] 9 | } 10 | -------------------------------------------------------------------------------- /website/CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Changelog 2 | 3 | All notable changes to this project will be documented in this file. 4 | 5 | The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), 6 | and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). 7 | 8 | ## \[Unreleased\] 9 | 10 | ### Added 11 | 12 | - added an initial version of the website 13 | -------------------------------------------------------------------------------- /website/Dockerfile: -------------------------------------------------------------------------------- 1 | # build stage 2 | FROM node:18-alpine as build 3 | 4 | WORKDIR /app 5 | 6 | COPY . . 7 | RUN npm i 8 | RUN npm run build 9 | 10 | # run stage 11 | FROM node:18-alpine as run 12 | 13 | WORKDIR /app 14 | 15 | COPY --from=build /app/package*.json ./ 16 | COPY --from=build /app/build ./ 17 | COPY --from=build /app/.env ./ 18 | 19 | ARG PORT 20 | ENV PORT=$PORT 21 | EXPOSE $PORT 22 | 23 | CMD ["node", "./index.js"] 24 | -------------------------------------------------------------------------------- /website/README.md: -------------------------------------------------------------------------------- 1 | # Dotsync's website 2 | 3 | [![website-rex-deploy](https://github.com/mbaraa/dotsync/actions/workflows/website-rex-deploy.yml/badge.svg)](https://github.com/mbaraa/dotsync/actions/workflows/website-rex-deploy.yml) 4 | 5 | This is the website of [Dotsync](https://github.com/mbaraa/dotsync), IDK, it seems cute to have a landing page and docs outside the repo's readme! 6 | 7 | ### Run it locally: 8 | 9 | I have no idea why you'd wanna do this, but here'e how: 10 | 11 | #### You'll need: 12 | 13 | 1. Docker and Docker Compose 14 | 1. An internet connection 15 | 1. Sveltekit builds fast so you won't need as much time as the server... 16 | 17 | #### You'll do: 18 | 19 | 1. Clone the repo 20 | 1. Run `docker compose up -d` 21 | 22 | - (Optional) use [Rex](https://github.com/mbaraa/rex) for a minimalistic deployment automation. 23 | -------------------------------------------------------------------------------- /website/docker-compose.yml: -------------------------------------------------------------------------------- 1 | version: "3.8" 2 | 3 | services: 4 | dotsync-website: 5 | build: 6 | dockerfile: Dockerfile 7 | context: "." 8 | restart: "always" 9 | environment: 10 | - PORT=8082 11 | ports: 12 | - "2389:8082" 13 | stdin_open: true 14 | -------------------------------------------------------------------------------- /website/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "dotsync_website", 3 | "version": "0.0.1", 4 | "private": true, 5 | "scripts": { 6 | "dev": "vite dev --host --port 8081", 7 | "build": "vite build", 8 | "preview": "vite preview", 9 | "check": "svelte-kit sync && svelte-check --tsconfig ./tsconfig.json", 10 | "check:watch": "svelte-kit sync && svelte-check --tsconfig ./tsconfig.json --watch", 11 | "lint": "prettier --plugin-search-dir . --check .", 12 | "format": "prettier --plugin-search-dir . --write ." 13 | }, 14 | "devDependencies": { 15 | "@sveltejs/adapter-node": "^1.3.1", 16 | "@sveltejs/kit": "^1.20.4", 17 | "autoprefixer": "^10.4.14", 18 | "postcss": "^8.4.24", 19 | "postcss-load-config": "^4.0.1", 20 | "prettier": "^2.8.0", 21 | "prettier-plugin-svelte": "^2.10.1", 22 | "svelte": "^4.0.5", 23 | "svelte-check": "^3.4.3", 24 | "tailwindcss": "^3.3.2", 25 | "tslib": "^2.4.1", 26 | "typescript": "^5.0.0", 27 | "vite": "^4.4.2" 28 | }, 29 | "type": "module", 30 | "dependencies": {} 31 | } 32 | -------------------------------------------------------------------------------- /website/postcss.config.cjs: -------------------------------------------------------------------------------- 1 | const tailwindcss = require("tailwindcss"); 2 | const autoprefixer = require("autoprefixer"); 3 | 4 | const config = { 5 | plugins: [ 6 | //Some plugins, like tailwindcss/nesting, need to run before Tailwind, 7 | tailwindcss(), 8 | //But others, like autoprefixer, need to run after, 9 | autoprefixer 10 | ] 11 | }; 12 | 13 | module.exports = config; 14 | -------------------------------------------------------------------------------- /website/src/app.d.ts: -------------------------------------------------------------------------------- 1 | // See https://kit.svelte.dev/docs/types#app 2 | // for information about these interfaces 3 | declare global { 4 | namespace App { 5 | // interface Error {} 6 | // interface Locals {} 7 | // interface PageData {} 8 | // interface Platform {} 9 | } 10 | } 11 | 12 | export {}; 13 | -------------------------------------------------------------------------------- /website/src/app.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | Dotsync 7 | 8 | 9 | 13 | 14 | %sveltekit.head% 15 | 16 | 17 |
%sveltekit.body%
18 | 19 | 20 | -------------------------------------------------------------------------------- /website/src/app.postcss: -------------------------------------------------------------------------------- 1 | /* Write your global styles here, in PostCSS syntax */ 2 | @tailwind base; 3 | @tailwind components; 4 | @tailwind utilities; 5 | 6 | html, 7 | div { 8 | scroll-behavior: smooth; 9 | } 10 | 11 | @layer base { 12 | @font-face { 13 | font-family: "Terminus"; 14 | font-weight: 400; 15 | font-style: normal; 16 | font-display: swap; 17 | src: url("/fonts/TerminusTTF-4.49.3.ttf"); 18 | } 19 | 20 | @font-face { 21 | font-family: "Terminus"; 22 | font-weight: 700; 23 | font-style: normal; 24 | font-display: swap; 25 | src: url("/fonts/TerminusTTF-Bold-4.49.3.ttf"); 26 | } 27 | 28 | @font-face { 29 | font-family: "Terminus"; 30 | font-weight: 400; 31 | font-style: italic; 32 | font-display: swap; 33 | src: url("/fonts/TerminusTTF-Italic-4.49.3.ttf"); 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /website/src/lib/components/ButtonLink.svelte: -------------------------------------------------------------------------------- 1 | 5 | 6 | 7 | 11 | 12 | -------------------------------------------------------------------------------- /website/src/lib/components/CodeSnippet.svelte: -------------------------------------------------------------------------------- 1 | 10 | 11 |
12 | {code} 13 | 29 |
30 | -------------------------------------------------------------------------------- /website/src/lib/components/DocsIntro.svelte: -------------------------------------------------------------------------------- 1 | 4 | 5 |
6 |
7 | Dotsync 14 |
15 | 16 |

17 | This is Dotsync's "official" documentation 📚 18 |

19 | 20 | 34 | 35 |
36 | Dotsync - A simple and concise dotfiles synchronizer! | Product Hunt 47 |
48 |
49 | -------------------------------------------------------------------------------- /website/src/lib/components/Footer.svelte: -------------------------------------------------------------------------------- 1 |
2 |

3 | This website is open source and you can fork it on GitHub 7 |

8 |
9 | -------------------------------------------------------------------------------- /website/src/lib/components/Intro.svelte: -------------------------------------------------------------------------------- 1 | 4 | 5 |
6 |
7 | Dotsync 14 |
15 | 16 |

17 | Dotsync is a small, free, open-source and blazingly fast dotfiles synchronizer! 18 |

19 | 20 | 28 | 29 |
30 | Dotsync - A simple and concise dotfiles synchronizer! | Product Hunt 41 |
42 |
43 | -------------------------------------------------------------------------------- /website/src/lib/components/Section.svelte: -------------------------------------------------------------------------------- 1 | 7 | 8 |
9 |

{title}

10 | {#if subTitle.length > 0} 11 |

{subTitle}

12 | {/if} 13 | 14 | 15 |
16 | -------------------------------------------------------------------------------- /website/src/lib/index.ts: -------------------------------------------------------------------------------- 1 | // place files you want to import through the `$lib` alias in this folder. 2 | -------------------------------------------------------------------------------- /website/src/routes/+layout.svelte: -------------------------------------------------------------------------------- 1 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /website/src/routes/+page.svelte: -------------------------------------------------------------------------------- 1 | 20 | 21 | 22 | Dotsync 23 | 24 | 25 |
26 | 27 | 28 |
29 |
30 |
31 |

Efficient

32 |

33 | The shining point in Dotsync. synchronizing dotfiles in a seamless way, instead of copying 34 | dotfile back and forth to/from a Git repository. 35 |

36 |
37 |
38 |

Lightweight

39 |

40 | The standalone CLI is only 5MiB, and takes ≲ 41 | 10MiB of memory while running! 42 |

43 |
44 |
45 |

Blazingly Fast

46 |

47 | Well that's a bit stretching it, but since it’s really simple and straight to the point, 48 | so performance is just snappy! 49 |

50 |
51 |
52 |

Cool Stack

53 |

54 | This is my favorite thing, Dotsync has a very fancy and diverse stack, where The CLI 59 | is built with Go, 60 | the 61 | server 64 | with Elixir 65 | & 66 | Phoenix, 67 | and the 68 | website 71 | with SvelteKit 72 | & 73 | Tailwind CSS. 74 |

75 |
76 |
77 |

Open-source

78 | Dotsync is an open-source project licensed under 79 | GPL-3.0, you can star it, fork it, open an issue, or make a pull request at any repository you 84 | desire ❴CLI, 89 | server, 92 | website❵ 95 |

96 |

97 |
98 |

Free

99 |

100 | Dotsync is completely free (as in both freedom and charge), but I wouldn't mind if you got 101 | me a coffee. 104 |

105 |
106 |
107 |
108 | 109 |
113 |

Use the Go installer for a quick installaion.

114 | 115 | 116 |

Add Go's bin path to your path.

117 | > ~/.`basename $SHELL`rc'} /> 118 |
119 | 120 |
126 |

127 | 1. Login using an email, and follow the steps 128 |

129 | 130 | 131 |

132 | 2. Add a file to your sync list, for example ~/.bashrc 133 |

134 | 135 | 136 |

137 | 3. Login into another computer 138 |
139 | 4. Sync the files on the other computer 140 |

141 | 142 | 143 |

144 | 5. Update your files after a local change 145 |

146 | 147 | 148 |

149 | For a more detailed usage visit the Docs 150 |

151 |
152 | 153 |
154 |

155 | 1. Dotsync doesn't include any kind of data telemetry trackers, your data are encrypted inside 156 | of a docker container, so chances of a leakage are near zero! 157 |

158 |

159 | 2. I herby promise to never look at any of the uploaded data, I don't know if it means 160 | anything, but trust me, I ain't got time to browse containers and decrypt files manually. 161 |

162 |

163 | 3. Finally, as you can see Dotsync is open source, and you can check how the data flows across 164 | the application. 165 |

166 |
167 | 168 |
169 |
170 | -------------------------------------------------------------------------------- /website/src/routes/api/latest-release/+server.ts: -------------------------------------------------------------------------------- 1 | import type { RequestEvent, RequestHandler } from "@sveltejs/kit"; 2 | import { GITHUB_TOKEN } from "$env/static/private"; 3 | 4 | export const GET: RequestHandler = async (_event: RequestEvent) => { 5 | const tag = await fetch("https://api.github.com/repos/mbaraa/dotsync/releases", { 6 | method: "GET", 7 | headers: { 8 | Authorization: `Bearer ${GITHUB_TOKEN}`, 9 | "X-GitHub-Api-Version": "2022-11-28", 10 | Accept: "application/vnd.github+json" 11 | } 12 | }) 13 | .then((resp) => resp.json()) 14 | .then((resp) => { 15 | if (resp && resp.length > 0 && resp[0]["tag_name"]) { 16 | return resp[0]["tag_name"]; 17 | } 18 | return "latest"; 19 | }) 20 | .catch(() => { 21 | return "latest"; 22 | }); 23 | 24 | return new Response(JSON.stringify({ tag }), { status: 200 }); 25 | }; 26 | -------------------------------------------------------------------------------- /website/src/routes/docs/+page.svelte: -------------------------------------------------------------------------------- 1 | 7 | 8 | 9 | Dotsync's Docs 10 | 11 | 12 |
13 | 14 | 15 |
16 |

17 | You can create an account, or login into your existing account using the `-login` flag followed by a valid email of yours. 20 |

21 |

22 | For example, logging in with this email dotsync@mbaraa.com 23 |

24 | 25 | 26 |

27 | Email might be delivered to spam, since it contains a JWT which some email providers consider a malicious email. 32 |

33 | 34 |

35 | Account deletion is possible using the CLI via the `-delete-user` flag with 36 | no arguments, deleting your account will delete your files from the remote server, 37 | but it won't affect your local files. 38 |

39 | 40 | 41 |

42 | Account deletion will fail if you're not logged in, since there's no account to delete... 43 |

44 |
45 | 46 |
47 |

48 | To add a file to the sync list simply use the `-add` flag followed by the 49 | file's path, 50 |
51 | Make sure that you a read/write access to the file so that it can be written on and read when synchronizing 52 | all files. 53 |

54 |

55 | All files are stored with their absolute path, this is to avoid any conflicts like 56 | ~/.config/i3/config 57 | and ~/.config/polybar/config, if both of them were added using `dotsync -add 58 | config` they will be stored with their absolute. 59 |

60 |

61 | Uploaded files have a 256KiB size limit, since it's more than enough for a dotfile, and if 63 | it was larger Dotsync wouldn't be free... 65 |

66 |

67 | For example, adding the file ~/.config/i3/config 68 |

69 | 70 | 71 |

72 | This will add all sub files and directories inside of ~/.config/i3, so 73 | careful with the files' sizes! 74 |

75 | 76 |

77 | Adding a file will fail if you're not logged in! 78 |

79 |
80 | 81 |
82 |

83 | Adding a directory to the sync list is similar to adding a file using the `-add` flag followed by the directory's path. 86 |

87 |

88 | The size limit of a directory is 50MiB with each file not larger than 256KiB. 89 |
90 |

91 |

92 | For example, adding the directory ~/.config/i3 93 |

94 | 95 | 96 |

97 | Adding a directory will fail if you're not logged in! 98 |

99 |
100 | 101 |
102 |

103 | To remove a file from the sync list simply use the `-remove` flag followed by 104 | the file's path (same as in the sync list), 105 |
106 | Make sure that the file exists in the sync list. 107 |

108 |

109 | For example, removing the file ~/.config/i3/config 110 |

111 | 112 | 113 |

114 | Deleting a file will fail if you're not logged in! 115 |

116 |
117 | 118 |
119 |

120 | Same as files, but with a directory name, where the server will do its magic! 121 |

122 |

124 | 125 |
126 |

127 | To list current synced files use the `-list` flag with no arguments. 128 |

129 | 130 | 131 |

132 | This will list all synced files in a chronological order, with their absolute path, so that 133 | you know what is synced. 134 |

135 | 136 |

137 | Listing synced files will fail if you're not logged in! 138 |

139 |
140 | 141 |
142 |

143 | To synchronize files from your computer to the remote server use the `-upload` flag with no arguments. 145 |

146 | 147 | 148 |

149 | Uploading changed files follows the same rules that apply for uploading a single file, 150 | that is each updated file should not be larger than 256KiB. 151 |

152 | 153 |

154 | Uploading files will fail if you're not logged in! 155 |

156 |
157 | 158 |
159 |

160 | To download currently synced files from the remote server to your computer use the `-download` flag with no arguments. 163 |

164 | 165 | 166 |

167 | When downloading synced files, if a file doesn't exist Dotsync will create a file with the 168 | remote server's version, so this can be very handy with fresh installs, just saying 👀 169 |

170 | 171 |

172 | Downloading synced files will fail if you're not logged in! 173 |

174 |
175 | 176 |
182 |

183 | A handy Makefile is included to do a proper build from source. 186 |

187 |

188 | Just compile the project, this will take no time, given the fact that the Go compiler is 189 | blaznigly fast. 190 |

191 | 192 | 193 |

Installing the compiled binary.

194 | 195 | 196 |

Compiling and installing the latest release from the remote repository.

197 | 198 |
199 | 200 |
201 |

202 | For now just open the server's repo and check the controllers... 207 |

208 |
209 | 210 |
211 |

212 | Do it if you want, start with this section please! 213 |

214 |
215 | 216 |
218 | -------------------------------------------------------------------------------- /website/static/favicon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mbaraa/dotsync/e6c2f5eed6bcaad12f893ff735c75ec7f97f2183/website/static/favicon.png -------------------------------------------------------------------------------- /website/static/fonts/TerminusTTF-4.49.3.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mbaraa/dotsync/e6c2f5eed6bcaad12f893ff735c75ec7f97f2183/website/static/fonts/TerminusTTF-4.49.3.ttf -------------------------------------------------------------------------------- /website/static/fonts/TerminusTTF-Bold-4.49.3.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mbaraa/dotsync/e6c2f5eed6bcaad12f893ff735c75ec7f97f2183/website/static/fonts/TerminusTTF-Bold-4.49.3.ttf -------------------------------------------------------------------------------- /website/static/fonts/TerminusTTF-Italic-4.49.3.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mbaraa/dotsync/e6c2f5eed6bcaad12f893ff735c75ec7f97f2183/website/static/fonts/TerminusTTF-Italic-4.49.3.ttf -------------------------------------------------------------------------------- /website/static/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mbaraa/dotsync/e6c2f5eed6bcaad12f893ff735c75ec7f97f2183/website/static/logo.png -------------------------------------------------------------------------------- /website/svelte.config.js: -------------------------------------------------------------------------------- 1 | import adapter from "@sveltejs/adapter-node"; 2 | import { vitePreprocess } from "@sveltejs/kit/vite"; 3 | 4 | /** @type {import('@sveltejs/kit').Config} */ 5 | const config = { 6 | preprocess: [vitePreprocess({})], 7 | 8 | kit: { adapter: adapter() } 9 | }; 10 | 11 | export default config; 12 | -------------------------------------------------------------------------------- /website/tailwind.config.cjs: -------------------------------------------------------------------------------- 1 | /** @type {import('tailwindcss').Config}*/ 2 | const config = { 3 | content: ["./src/**/*.{html,js,svelte,ts}"], 4 | 5 | theme: { 6 | extend: { 7 | fontFamily: { 8 | Terminus: ["Terminus", "sans"] 9 | } 10 | } 11 | }, 12 | 13 | plugins: [] 14 | }; 15 | 16 | module.exports = config; 17 | -------------------------------------------------------------------------------- /website/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "./.svelte-kit/tsconfig.json", 3 | "compilerOptions": { 4 | "allowJs": true, 5 | "checkJs": true, 6 | "esModuleInterop": true, 7 | "forceConsistentCasingInFileNames": true, 8 | "resolveJsonModule": true, 9 | "skipLibCheck": true, 10 | "sourceMap": true, 11 | "strict": true 12 | } 13 | // Path aliases are handled by https://kit.svelte.dev/docs/configuration#alias 14 | // 15 | // If you want to overwrite includes/excludes, make sure to copy over the relevant includes/excludes 16 | // from the referenced tsconfig.json - TypeScript does not merge them in 17 | } 18 | -------------------------------------------------------------------------------- /website/vite.config.ts: -------------------------------------------------------------------------------- 1 | import { sveltekit } from "@sveltejs/kit/vite"; 2 | import { defineConfig } from "vite"; 3 | 4 | export default defineConfig({ 5 | plugins: [sveltekit()] 6 | }); 7 | --------------------------------------------------------------------------------