{"id":24122206,"url":"https://github.com/tlinden/ephemerup","last_synced_at":"2026-01-11T23:53:56.552Z","repository":{"id":142775462,"uuid":"601746625","full_name":"TLINDEN/ephemerup","owner":"TLINDEN","description":"Ephemeral file upload server with API and cli","archived":false,"fork":false,"pushed_at":"2023-10-01T13:58:02.000Z","size":879,"stargazers_count":0,"open_issues_count":0,"forks_count":0,"subscribers_count":2,"default_branch":"main","last_synced_at":"2025-01-11T11:41:51.331Z","etag":null,"topics":["command-line","file-sharing","golang","http","restapi","uploader"],"latest_commit_sha":null,"homepage":"","language":"Go","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"gpl-3.0","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/TLINDEN.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null}},"created_at":"2023-02-14T18:22:50.000Z","updated_at":"2023-03-29T11:07:26.000Z","dependencies_parsed_at":"2024-06-20T23:25:39.456Z","dependency_job_id":"885d1b0f-3ddd-4694-870a-e9b030565ce9","html_url":"https://github.com/TLINDEN/ephemerup","commit_stats":null,"previous_names":["tlinden/cenophane","tlinden/up"],"tags_count":3,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/TLINDEN%2Fephemerup","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/TLINDEN%2Fephemerup/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/TLINDEN%2Fephemerup/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/TLINDEN%2Fephemerup/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/TLINDEN","download_url":"https://codeload.github.com/TLINDEN/ephemerup/tar.gz/refs/heads/main","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":241210941,"owners_count":19927817,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["command-line","file-sharing","golang","http","restapi","uploader"],"created_at":"2025-01-11T11:38:58.710Z","updated_at":"2025-11-24T17:03:04.609Z","avatar_url":"https://github.com/TLINDEN.png","language":"Go","readme":"[![Actions](https://github.com/tlinden/ephemerup/actions/workflows/ci.yaml/badge.svg)](https://github.com/tlinden/ephemerup/actions)\n[![License](https://img.shields.io/badge/license-GPL-blue.svg)](https://github.com/tlinden/ephemerup/blob/master/LICENSE)\n[![Go Report Card](https://goreportcard.com/badge/github.com/tlinden/ephemerup)](https://goreportcard.com/report/github.com/tlinden/ephemerup)\n\n# ephemerup\nSimple standalone file upload server with expiration and commandline client.\n\n## Introduction\n\n**ephemerup** is a simple standalone  file server where every uploaded\nfile expires  sooner or later. The  server provides a RESTful  API and\ncan be used easily with the commandline client `upctl`.\n\nThe idea is to provide a way to quickly exchange files between parties\nwhen  no other  way  is available  and the  files  themselfes are  not\nimportant enough to  keep them around. Think of  this szenario: you're\nworking for  the network departement  and there's a problem  with your\nrouting. Tech support  asks you to create a network  trace and send it\nto  them. But  you  can't because  the  trace file  is  too large  and\nsensitive to  be sent by email.  This is where **ephemerup**  comes to\nthe rescue.\n\nYou upload the  file, send the download  url to the other  party and -\nassuming you've utilized  the defaults - when they download  it, it is\nbeing deleted  immediately from the  server. But  you can also  set an\nexpire time, say 5 days or something like that.\n\nThe  download urls  generated  by **ephemerup**  consist  of a  unique\nonetime  hash, so  they  are somewhat  confident.  However, if  you're\nuploading really sensitive data, you better encrypt it.\n\n**ephemerup** also  supports something we  call an API  Context. There\ncan be many such API contexts.  Each of these has an associated token,\nwhich  has  to be  used  by  legitimate  clients to  authenticate  and\nauthorize. A user  can only manage uploads within  that context. Think\n\"tenant\" if you will.\n\n## Demo\n\n![demo upctl session](demo/upctl.gif)\n\n## Features\n\n- RESTful API\n- Authentication and Authorization through bearer api token\n- multiple tenants supported (tenant == api context)\n- Each upload gets its own unique id\n- download uri is public, no api required, it is intended for end users\n- uploads may consist of one or multiple files\n- zipped automatically\n- uploads expire, either as soon as it gets downloaded or when a timer runs out\n- the command line client uses the api\n- configuration using HCL language\n- docker container build available\n- the server supports config by config file, environment variables or flags\n- restrictive defaults\n\n## Installation\n\n### Deploy server using pre-built docker file\n\nA   ready   to   use   ephemerup  server   image   is   available   on\n[ghcr.io](https://ghcr.io/tlinden/ephemerup).   Supported  tags   are:\n`latest` or a github release tag.\n\nTo try it locally with docker:\n\n```\ndocker run -dp 8080:8080 --name eph \\\n     ghcr.io/tlinden/ephemerup:latest \\\n    -LogLevel=info\n```\n\n### Build Dockerfile\n\nThere's a `Dockerfile` available for the server so you can build and run it using docker:\n```\nmake buildimage\ndocker-compose run ephemerup\n```\nThen use the client to test it.\n\n### Install from binary package\n\nGo  to  the  [Releases](https://github.com/TLINDEN/ephemerup/releases)\npage and download the latest tarball  for your platform. Unpack it and\nexecute `make install` inside the created directory.\n\nThis installs both the server `ephemerupd` and the client `upctl`.\n\nIf you  only need the client,  just grab the tarball  and extract just\nthe client, copy it to your bin folder and you're good to go.\n\n### Deploy on Kubernetes using the Helm chart\n\n```\nhelm repo add tlinden https://tlinden.github.io/ephemerup/\nhelm repo update\nhelm upgrade --install ephemerup tlinden/ephemerup --namespace ephemerup --create-namespace\n```\n\nRefer to the [chart documentation](https://github.com/TLINDEN/ephemerup/tree/main/charts/ephemerup) for help.\n\nFor starters, create a minimal `values.yaml` like this one:\n```yaml\nimage:\n  tag: \"v0.0.3\"\n```\n\n**Please note that the helm chart doesn't deploy a loadbalancer, you need to do this yourself, if needed.**\n\n### Build from source\n\nTo build from source, you'll  need  a go  build  environment.\n\nClone the git repo\nJust run `make` to build everything.\n\n\n## Server Usage\n\n```\nephemerupd -h\n      --apikeys strings     Api key[s] to allow access\n  -a, --apiprefix string    API endpoint path (default \"/api\")\n  -n, --appname string      App name to say hi as (default \"ephemerupd v0.0.1\")\n  -b, --bodylimit int       Max allowed upload size in bytes (default 10250000000)\n  -c, --config string       custom config file\n  -D, --dbfile string       Bold database file to use (default \"/tmp/uploads.db\")\n  -d, --debug               Enable debugging\n      --frontpage string    Content or filename to be displayed on / in case someone visits (default \"welcome to upload api, use /api enpoint!\")\n  -4, --ipv4                Only listen on ipv4\n  -6, --ipv6                Only listen on ipv6\n  -l, --listen string       listen to custom ip:port (use [ip]:port for ipv6) (default \":8080\")\n  -p, --prefork             Prefork server threads\n  -s, --storagedir string   storage directory for uploaded files (default \"/tmp\")\n      --super string        The API Context which has permissions on all contexts\n  -u, --url string          HTTP endpoint w/o path\n  -v, --version             Print program version\n```\n\nAll flags can be set using environment variables, prefix the flag with `EPHEMERUPD_` and uppercase it, eg: \n```\nEPHEMERUPD_LISTEN=:8080\n```\n\nIn addition it is possible to set api contexts using env vars (otherwise only possible using the config file):\n```\nEPHEMERUPD_CONTEXT_SUPPORT=\"support:tymag-fycyh-gymof-dysuf-doseb-puxyx\"\nEPHEMERUPD_CONTEXT_FOOBAR=\"foobar:U3VuIE1hciAxOSAxMjoyNTo1NyBQTSBDRVQgMjAyMwo\"\n```\n\nConfiguration can also be done using a config file (searched in the following locations):\n- `/etc/ephemerupd.hcl`\n- `/usr/local/etc/ephemerupd.hcl`\n- `~/.config/ephemerupd/ephemerupd.hcl`\n- `~/.ephemerupd`\n- `$(pwd)/ephemerupd.hcl`\n\nOr using the flag `-c`. Sample config file:\n```\nlisten = \":8080\"\nbodylimit = 10000\n\napicontext = [\n  {\n    context = \"root\"\n    key = \"0fddbff5d8010f81cd28a7d77f3e38981b13d6164c2fd6e1c3f60a4287630c37\",\n  },\n  {\n    context = \"foo\",\n    key = \"970b391f22f515d96b3e9b86a2c62c627968828e47b356994d2e583188b4190a\"\n  }\n]\n\n#url = \"https://sokrates.daemon.de\"\n\n# this is the root context with all permissions\nsuper = \"root\"\n```\n\n### Server endpoint\n\nThe   server   serves   the   API  under   the   following   endpoint:\n`http://SERVERNAME[:PORT]/api/v1`  where   SERVERNAME[:PORT]  is  the\nargument  to  the  `-l`  commandline argument  or  the  config  option\n`listen` or the environment variable `EPHEMERUPD_LISTEN`.\n\nBy default  the server listens  on any interface  ip4 and ipv6  on TCP\nport  8080. You  can  specify a  server  name or  an  ipaddress and  a\nport. The server can be configured to run on ipv6 (or ipv4) only using\nthe `-4` respective the `-6` commandline flags.\n\nIt does not  support TLS at the  moment. Use a nginx  reverse proxy in\nfront of it.\n\n### Server REST API\n\nEvery endpoint returns a JSON object. Each returned object contains the data requested plus:\n\n- success: true or false\n- code: HTTP Response Code\n- message: error message, if success==false\n\n#### Endpoints\n\n| HTTP Method | Endpoint              | Parameters          | Input                      | Returns                               | Description                                   |\n|-------------|-----------------------|---------------------|----------------------------|---------------------------------------|-----------------------------------------------|\n| GET         | /v1/uploads           | apicontext,q,expire |                            | List of upload objects                | list upload objects                           |\n| POST        | /v1/uploads           |                     | multipart-formdata file[s] | List of 1 upload object if successful | upload a file and create a new upload object  |\n| GET         | /v1/uploads/{id}      |                     |                            | List of 1 upload object if successful | list one specific upload object matching {id} |\n| DELETE      | /v1/uploads/{id}      |                     |                            | Noting                                | delete an upload object identified by {id}    |\n| PUT         | /v1/uploads/{id}      |                     | JSON upload object         | List of 1 upload object if successful | modify an upload object identified by {id}    |\n| GET         | /v1/uploads/{id}/file |                     |                            | File download                         | Download the file associated with the  object |\n| GET         | /v1/forms             | apicontext,q,expire |                            | List of form objects                  | list form objects                             |\n| POST        | /v1/forms             |                     | JSON form object           | List of 1 form object if successful   | create a new form object                      |\n| GET         | /v1/forms/{id}        |                     |                            | List of 1 form object if successful   | list one specific form object matching {id}   |\n| DELETE      | /v1/forms/{id}        |                     |                            | Noting                                | delete an form object identified by {id}      |\n| PUT         | /v1/forms/{id}        |                     | JSON form object           | List of 1 form object if successful   | modify an form object identified by {id}      |\n\n#### Consumer URLs\n\nThe following endpoints are no API urls, but accessed directly by consumers using their browser or `wget` etc:\n\n| URL                     | Description                                             |\n|-------------------------|---------------------------------------------------------|\n| /                       | Display a short welcome message, can be customized      |\n| /download/{id}[/{file}] | Download link returned after an upload has been created |\n| /form/{id}              | Upload form for consumer                                |\n\n#### API Objects\n\nResponse:\n\n| Field   | Data Type | Description                           |\n|---------|-----------|---------------------------------------|\n| success | bool      | if true the request was successful    |\n| code    | int       | HTTP response code                    |\n| message | string    | error message, if any                 |\n| uploads | array     | list of upload objects (may be empty) |\n| forms   | array     | list of form objects (may be empty)   |\n\nUpload:\n\n| Field    | Data Type        | Description                                                                                                                                 |\n|----------|------------------|---------------------------------------------------------------------------------------------------------------------------------------------|\n| id       | string           | unique identifier for the object                                                                                                            |\n| expire   | string           | when the upload has to expire, either \"asap\" or a Duration using numbers and the letters d,h,m,s (days,hours,minutes,seconds), e.g. 2d4h30m |\n| file     | string           | filename after uploading, this is what a consumer gets when downloading it                                                                  |\n| members  | array of strings | list of the original filenames                                                                                                              |\n| created  | timestamp        | time of object creation                                                                                                                     |\n| context  | string           | the API context the upload has been created under                                                                                           |\n| url      | string           | the download URL                                                                                                                            |\n\nForm:\n\n| Field       | Data Type | Description                                                                                                                               |\n|-------------|-----------|-------------------------------------------------------------------------------------------------------------------------------------------|\n| id          | string    | unique identifier for the object                                                                                                          |\n| expire      | string    | when the form has to expire, either \"asap\" or a Duration using numbers and the letters d,h,m,s (days,hours,minutes,seconds), e.g. 2d4h30m |\n| description | string    | arbitrary description, shown on the form page                                                                                             |\n| context     | string    | the API context the form has been created under and the uploaded files will be created on                                                 |\n| notify      | string    | email address of the form creator, who gets an email once the consumer has uploaded files using the form                                  |\n| created     | timestamp | time of object creation                                                                                                                   |\n| url         | string    | the form URL                                                                                                                              |\n\nNote: if the expire field for a form  is not set or set to \"asap\" only\n1 upload  object can be created  from it.  However, if  a duration has\nbeen specified, the  form can be used multiple times  and thus creates\nmultiple upload objects.\n\n\n\n## Client Usage\n\n```\nupctl \nError: No command specified!\nUsage:\n  upctl [options] [flags]\n  upctl [command]\n\nAvailable Commands:\n  completion  Generate the autocompletion script for the specified shell\n  delete      Delete an upload\n  describe    Describe an upload.\n  download    Download a file.\n  form        Form commands\n  help        Help about any command\n  list        List uploads\n  upload      Upload files\n\nFlags:\n  -a, --apikey string     Api key to use\n  -c, --config string     custom config file\n  -d, --debug             Enable debugging\n  -p, --endpoint string   upload api endpoint url (default \"http://localhost:8080/api/v1\")\n  -h, --help              help for upctl\n  -r, --retries int       How often shall we retry to access our endpoint (default 3)\n  -v, --version           Print program version\n\nUse \"upctl [command] --help\" for more information about a command.\n```\n\nThe client must be configured using a config file. The following locations are searched for it:\n- `$(pwd)/upctl.hcl`\n- `~/.config/upctl/upctl.hcl`\n\nSample config file for a client:\n```\nendpoint = \"http://localhost:8080/api/v1\"\napikey = \"970b391f22f515d96b3e9b86a2c62c627968828e47b356994d2e583188b4190a\"\n```\n\nThe `endpoint` is  the **ephemerup** server running  somewhere and the\n`apikey` is the token you got from the server operator..\n\n\n## TODO\n\n- add metrics (as in https://github.com/ansrivas/fiberprometheus)\n- do not manually generate output urls, use fiber.GetRoute()\n- upd: https://docs.gofiber.io/guide/error-handling/ to always use json output\n- add (default by time!) sorting to list outputs, and add sort flag\n\n\n## BUGS\n\n### upctl HTTP 413 weird behavior\n\n- with -d reports correctly the 413, w/o it, it reports the timeout before.\n\n## curl commands\n\n### upload\n\n```\ncurl -X POST localhost:8080/api/putfile -F \"upload[]=@xxx\" -F \"upload[]=@yyy\" -H \"Content-Type: multipart/form-data\"\n```\n\n### download\n```\ncurl -O http://localhost:8080/api/v1/file/388f41f4-3f0d-41e1-a022-9132c0e9e16f/2023-02-28-18-33-xxx\n```\n\n### delete\n```\ncurl -X DELETE http://localhost:8080/api/v1/file/388f41f4-3f0d-41e1-a022-9132c0e9e16f/\ncurl -X DELETE http://localhost:8080/api/v1/file/?id=388f41f4-3f0d-41e1-a022-9132c0e9e16f/\ncurl -X DELETE -H \"Accept: application/json\"  -d '{\"id\":\"$id\"}' http://localhost:8080/api/v1/file/\n```\n","funding_links":[],"categories":[],"sub_categories":[],"project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Ftlinden%2Fephemerup","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Ftlinden%2Fephemerup","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Ftlinden%2Fephemerup/lists"}